mirror of
				https://gitlab.labs.nic.cz/labs/bird.git
				synced 2024-05-11 16:54:54 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			3127 lines
		
	
	
		
			81 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			3127 lines
		
	
	
		
			81 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 *	BIRD -- Protocols
 | 
						|
 *
 | 
						|
 *	(c) 1998--2000 Martin Mares <mj@ucw.cz>
 | 
						|
 *
 | 
						|
 *	Can be freely distributed and used under the terms of the GNU GPL.
 | 
						|
 */
 | 
						|
 | 
						|
#undef LOCAL_DEBUG
 | 
						|
 | 
						|
#include "nest/bird.h"
 | 
						|
#include "nest/protocol.h"
 | 
						|
#include "lib/resource.h"
 | 
						|
#include "lib/lists.h"
 | 
						|
#include "lib/event.h"
 | 
						|
#include "lib/timer.h"
 | 
						|
#include "lib/string.h"
 | 
						|
#include "conf/conf.h"
 | 
						|
#include "nest/route.h"
 | 
						|
#include "nest/iface.h"
 | 
						|
#include "nest/mpls.h"
 | 
						|
#include "nest/cli.h"
 | 
						|
#include "filter/filter.h"
 | 
						|
#include "filter/f-inst.h"
 | 
						|
 | 
						|
pool *proto_pool;
 | 
						|
static TLIST_LIST(proto) global_proto_list;
 | 
						|
 | 
						|
static list STATIC_LIST_INIT(protocol_list);
 | 
						|
 | 
						|
#define CD(c, msg, args...) ({ if (c->debug & D_STATES) log(L_TRACE "%s.%s: " msg, c->proto->name, c->name ?: "?", ## args); })
 | 
						|
#define PD(p, msg, args...) ({ if (p->debug & D_STATES) log(L_TRACE "%s: " msg, p->name, ## args); })
 | 
						|
 | 
						|
static timer *gr_wait_timer;
 | 
						|
 | 
						|
#define GRS_NONE	0
 | 
						|
#define GRS_INIT	1
 | 
						|
#define GRS_ACTIVE	2
 | 
						|
#define GRS_DONE	3
 | 
						|
 | 
						|
static int graceful_restart_state;
 | 
						|
static u32 graceful_restart_locks;
 | 
						|
 | 
						|
static char *p_states[] = { "DOWN", "START", "UP", "STOP" };
 | 
						|
static char *c_states[] = { "DOWN", "START", "UP", "STOP", "RESTART" };
 | 
						|
 | 
						|
extern struct protocol proto_unix_iface;
 | 
						|
 | 
						|
static void proto_rethink_goal(struct proto *p);
 | 
						|
static char *proto_state_name(struct proto *p);
 | 
						|
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 int channel_refeed_prefilter(const struct rt_prefilter *p, const net_addr *n);
 | 
						|
static int channel_import_prefilter(const struct rt_prefilter *p, const net_addr *n);
 | 
						|
static void channel_feed_end(struct channel *c);
 | 
						|
static void channel_stop_export(struct channel *c);
 | 
						|
static void channel_export_stopped(struct rt_export_request *req);
 | 
						|
static void channel_refeed_stopped(struct rt_export_request *req);
 | 
						|
static void channel_check_stopped(struct channel *c);
 | 
						|
static void channel_reload_in_done(struct channel_import_request *cir);
 | 
						|
static void channel_request_partial_reload(struct channel *c, struct channel_import_request *cir);
 | 
						|
 | 
						|
static inline int proto_is_done(struct proto *p)
 | 
						|
{ return (p->proto_state == PS_DOWN) && proto_is_inactive(p); }
 | 
						|
 | 
						|
static inline int channel_is_active(struct channel *c)
 | 
						|
{ return (c->channel_state != CS_DOWN); }
 | 
						|
 | 
						|
static inline int channel_reloadable(struct channel *c)
 | 
						|
{
 | 
						|
  return c->reloadable && c->proto->reload_routes
 | 
						|
      || ((c->in_keep & RIK_PREFILTER) == RIK_PREFILTER);
 | 
						|
}
 | 
						|
 | 
						|
static inline void
 | 
						|
channel_log_state_change(struct channel *c)
 | 
						|
{
 | 
						|
  CD(c, "State changed to %s", c_states[c->channel_state]);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
channel_import_log_state_change(struct rt_import_request *req, u8 state)
 | 
						|
{
 | 
						|
  struct channel *c = SKIP_BACK(struct channel, in_req, req);
 | 
						|
  CD(c, "Channel import state changed to %s", rt_import_state_name(state));
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
channel_export_log_state_change(struct rt_export_request *req, u8 state)
 | 
						|
{
 | 
						|
  struct channel *c = SKIP_BACK(struct channel, out_req, req);
 | 
						|
  CD(c, "Channel export state changed to %s", rt_export_state_name(state));
 | 
						|
 | 
						|
  switch (state)
 | 
						|
  {
 | 
						|
    case TES_FEEDING:
 | 
						|
      if (c->proto->feed_begin)
 | 
						|
	c->proto->feed_begin(c);
 | 
						|
      break;
 | 
						|
    case TES_READY:
 | 
						|
      channel_feed_end(c);
 | 
						|
      break;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
channel_refeed_log_state_change(struct rt_export_request *req, u8 state)
 | 
						|
{
 | 
						|
  struct channel *c = SKIP_BACK(struct channel, refeed_req, req);
 | 
						|
  CD(c, "Channel export state changed to %s", rt_export_state_name(state));
 | 
						|
 | 
						|
  switch (state)
 | 
						|
  {
 | 
						|
    case TES_FEEDING:
 | 
						|
      if (c->proto->feed_begin)
 | 
						|
	c->proto->feed_begin(c);
 | 
						|
      break;
 | 
						|
    case TES_READY:
 | 
						|
      rt_stop_export(req, channel_refeed_stopped);
 | 
						|
      break;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
channel_dump_import_req(struct rt_import_request *req)
 | 
						|
{
 | 
						|
  struct channel *c = SKIP_BACK(struct channel, in_req, req);
 | 
						|
  debug("  Channel %s.%s import request %p\n", c->proto->name, c->name, req);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_dump_export_req(struct rt_export_request *req)
 | 
						|
{
 | 
						|
  struct channel *c = SKIP_BACK(struct channel, out_req, req);
 | 
						|
  debug("  Channel %s.%s export request %p\n", c->proto->name, c->name, req);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_dump_refeed_req(struct rt_export_request *req)
 | 
						|
{
 | 
						|
  struct channel *c = SKIP_BACK(struct channel, refeed_req, req);
 | 
						|
  debug("  Channel %s.%s refeed request %p\n", c->proto->name, c->name, req);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
channel_rpe_mark_seen_export(struct rt_export_request *req, struct rt_pending_export *rpe)
 | 
						|
{
 | 
						|
  channel_rpe_mark_seen(SKIP_BACK(struct channel, out_req, req), rpe);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_rpe_mark_seen_refeed(struct rt_export_request *req, struct rt_pending_export *rpe)
 | 
						|
{
 | 
						|
  channel_rpe_mark_seen(SKIP_BACK(struct channel, refeed_req, req), rpe);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
struct channel *
 | 
						|
channel_from_export_request(struct rt_export_request *req)
 | 
						|
{
 | 
						|
  if (req->dump_req == channel_dump_export_req)
 | 
						|
    return SKIP_BACK(struct channel, out_req, req);
 | 
						|
 | 
						|
  if (req->dump_req == channel_dump_refeed_req)
 | 
						|
    return SKIP_BACK(struct channel, refeed_req, req);
 | 
						|
 | 
						|
  bug("Garbled channel export request");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
proto_log_state_change(struct proto *p)
 | 
						|
{
 | 
						|
  if (p->debug & D_STATES)
 | 
						|
  {
 | 
						|
    char *name = proto_state_name(p);
 | 
						|
    if (name != p->last_state_name_announced)
 | 
						|
    {
 | 
						|
      p->last_state_name_announced = name;
 | 
						|
      PD(p, "State changed to %s", proto_state_name(p));
 | 
						|
    }
 | 
						|
  }
 | 
						|
  else
 | 
						|
    p->last_state_name_announced = NULL;
 | 
						|
}
 | 
						|
 | 
						|
struct channel_config *
 | 
						|
proto_cf_find_channel(struct proto_config *pc, uint net_type)
 | 
						|
{
 | 
						|
  struct channel_config *cc;
 | 
						|
 | 
						|
  WALK_LIST(cc, pc->channels)
 | 
						|
    if (cc->net_type == net_type)
 | 
						|
      return cc;
 | 
						|
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * proto_find_channel_by_table - find channel connected to a routing table
 | 
						|
 * @p: protocol instance
 | 
						|
 * @t: routing table
 | 
						|
 *
 | 
						|
 * Returns pointer to channel or NULL
 | 
						|
 */
 | 
						|
struct channel *
 | 
						|
proto_find_channel_by_table(struct proto *p, rtable *t)
 | 
						|
{
 | 
						|
  struct channel *c;
 | 
						|
 | 
						|
  WALK_LIST(c, p->channels)
 | 
						|
    if (c->table == t)
 | 
						|
      return c;
 | 
						|
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * proto_find_channel_by_name - find channel by its name
 | 
						|
 * @p: protocol instance
 | 
						|
 * @n: channel name
 | 
						|
 *
 | 
						|
 * Returns pointer to channel or NULL
 | 
						|
 */
 | 
						|
struct channel *
 | 
						|
proto_find_channel_by_name(struct proto *p, const char *n)
 | 
						|
{
 | 
						|
  struct channel *c;
 | 
						|
 | 
						|
  WALK_LIST(c, p->channels)
 | 
						|
    if (!strcmp(c->name, n))
 | 
						|
      return c;
 | 
						|
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * proto_add_channel - connect protocol to a routing table
 | 
						|
 * @p: protocol instance
 | 
						|
 * @cf: channel configuration
 | 
						|
 *
 | 
						|
 * This function creates a channel between the protocol instance @p and the
 | 
						|
 * routing table specified in the configuration @cf, making the protocol hear
 | 
						|
 * all changes in the table and allowing the protocol to update routes in the
 | 
						|
 * table.
 | 
						|
 *
 | 
						|
 * The channel is linked in the protocol channel list and when active also in
 | 
						|
 * the table channel list. Channels are allocated from the global resource pool
 | 
						|
 * (@proto_pool) and they are automatically freed when the protocol is removed.
 | 
						|
 */
 | 
						|
 | 
						|
struct channel *
 | 
						|
proto_add_channel(struct proto *p, struct channel_config *cf)
 | 
						|
{
 | 
						|
  struct channel *c = mb_allocz(proto_pool, cf->class->channel_size);
 | 
						|
 | 
						|
  c->name = cf->name;
 | 
						|
  c->class = cf->class;
 | 
						|
  c->proto = p;
 | 
						|
  c->table = cf->table->table;
 | 
						|
  rt_lock_table(c->table);
 | 
						|
 | 
						|
  c->in_filter = cf->in_filter;
 | 
						|
  c->out_filter = cf->out_filter;
 | 
						|
  c->out_subprefix = cf->out_subprefix;
 | 
						|
 | 
						|
  c->feed_block_size = cf->feed_block_size;
 | 
						|
 | 
						|
  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;
 | 
						|
  c->preference = cf->preference;
 | 
						|
  c->debug = cf->debug;
 | 
						|
  c->merge_limit = cf->merge_limit;
 | 
						|
  c->in_keep = cf->in_keep;
 | 
						|
  c->rpki_reload = cf->rpki_reload;
 | 
						|
 | 
						|
  c->channel_state = CS_DOWN;
 | 
						|
  c->last_state_change = current_time();
 | 
						|
  c->reloadable = 1;
 | 
						|
 | 
						|
  init_list(&c->roa_subscriptions);
 | 
						|
 | 
						|
  CALL(c->class->init, c, cf);
 | 
						|
 | 
						|
  add_tail(&p->channels, &c->n);
 | 
						|
 | 
						|
  CD(c, "Connected to table %s", c->table->name);
 | 
						|
 | 
						|
  return c;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
proto_remove_channel(struct proto *p UNUSED, struct channel *c)
 | 
						|
{
 | 
						|
  ASSERT(c->channel_state == CS_DOWN);
 | 
						|
 | 
						|
  CD(c, "Removed", c->name);
 | 
						|
 | 
						|
  rt_unlock_table(c->table);
 | 
						|
  rem_node(&c->n);
 | 
						|
  mb_free(c);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
proto_start_channels(struct proto *p)
 | 
						|
{
 | 
						|
  struct channel *c;
 | 
						|
  WALK_LIST(c, p->channels)
 | 
						|
    if (!c->disabled)
 | 
						|
      channel_set_state(c, CS_UP);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_pause_channels(struct proto *p)
 | 
						|
{
 | 
						|
  struct channel *c;
 | 
						|
  WALK_LIST(c, p->channels)
 | 
						|
    if (!c->disabled && channel_is_active(c))
 | 
						|
      channel_set_state(c, CS_PAUSE);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_stop_channels(struct proto *p)
 | 
						|
{
 | 
						|
  struct channel *c;
 | 
						|
  WALK_LIST(c, p->channels)
 | 
						|
    if (!c->disabled && channel_is_active(c))
 | 
						|
      channel_set_state(c, CS_STOP);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_remove_channels(struct proto *p)
 | 
						|
{
 | 
						|
  struct channel *c;
 | 
						|
  WALK_LIST_FIRST(c, p->channels)
 | 
						|
    proto_remove_channel(p, c);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * # Automatic ROA reloads
 | 
						|
 *
 | 
						|
 * Route origin authorizations may (and do) change over time by updates via
 | 
						|
 * our RPKI protocols. This then manifests in ROA tables. As the roa_check()
 | 
						|
 * is always executed on a specific contents of ROA table in a specific moment
 | 
						|
 * of time, its value may switch after updates in the ROA table and therefore
 | 
						|
 * must be re-evaluated any time the result may have changed.
 | 
						|
 *
 | 
						|
 * To enable this mechanism, there are auxiliary tools integrated in BIRD
 | 
						|
 * to automatically re-evaluate all filters that may get a different outcome
 | 
						|
 * after ROA change.
 | 
						|
 *
 | 
						|
 * ROA Subscription Data Structure (struct roa_subscription) is the connector
 | 
						|
 * between the channel and the ROA table, keeping track about unprocessed
 | 
						|
 * changes and initiating the reloads. The modus operandi is as follows:
 | 
						|
 *
 | 
						|
 * Init 1. Check whether the filter uses ROA at all.
 | 
						|
 * Init 2. Request exports from the ROA table
 | 
						|
 * Init 3. Allocate a trie
 | 
						|
 *
 | 
						|
 * Export from ROA: This may affect all routes for prefixes matching the ROA
 | 
						|
 * prefix, disregarding its maxlen. Thus we mark these routes in the request's
 | 
						|
 * auxiliary trie. Then we ping the settle timer to wait a reasonable amount of
 | 
						|
 * time before actually requesting channel reload.
 | 
						|
 *
 | 
						|
 * Settle timer fires when nothing has pinged it for the 'min' time, or 'max'
 | 
						|
 * time has elapsed since the first ping. It then:
 | 
						|
 *
 | 
						|
 * - requests partial channel import / export reload based on the trie
 | 
						|
 * - allocates a new trie
 | 
						|
 *
 | 
						|
 * As the import/export reload uses the auxiliary trie to prefilter prefixes,
 | 
						|
 * the trie must be freed after the reload is done, which is ensured in the
 | 
						|
 * .done() hook of the reimport/reexport request.
 | 
						|
 *
 | 
						|
 * # Channel export refeed
 | 
						|
 *
 | 
						|
 * The request, either by ROA or from CLI, is enqueued to the channel and an
 | 
						|
 * auxiliary export hook is requested from the table. This way, the ordinary
 | 
						|
 * updates can flow uninterrupted while refeed gets prefiltered by the given
 | 
						|
 * trie (if given). When the auxiliary export hook finishes, the .done() hook
 | 
						|
 * is then called for the requestor to do their cleanup.
 | 
						|
 *
 | 
						|
 * While refeeding, special care must be taken about route changes inside the
 | 
						|
 * table. For this, an auxiliary trie is allocated to keep track about already
 | 
						|
 * refed net, to avoid unnecessary multiple re-evaluation of filters.
 | 
						|
 *
 | 
						|
 * # Channel import reload from import table
 | 
						|
 *
 | 
						|
 * When the import table is on, the channel keeps the original version of the route
 | 
						|
 * in the table together with the actual version after filters, in a form of
 | 
						|
 * an additional layer of route attributes underneath the actual version. This makes
 | 
						|
 * it exceptionally simple to get the original version of the route directly
 | 
						|
 * from the table by an ordinary export which strips all the newer layers.
 | 
						|
 *
 | 
						|
 * Then, by processing all these auxiliary exports, the channel basically re-imports
 | 
						|
 * all the routes into the table back again, re-evaluating the filters and ROA checks.
 | 
						|
 *
 | 
						|
 * # Channel import reload from protocols
 | 
						|
 *
 | 
						|
 * When the import table is off, the protocol gets the reimport request directly
 | 
						|
 * via the .reload_routes() hook and must do its internal route reload instead.
 | 
						|
 * The protocol may not support it and in such case, this function returns 0
 | 
						|
 * indicating that no partial reload is going to happen. It's then on the
 | 
						|
 * developer's or user's discretion to run a full reload instead.
 | 
						|
 *
 | 
						|
 * # Caveats, FIXME's, TODO's and other kinds of hell
 | 
						|
 *
 | 
						|
 * The partial reexport uses a trie to track state for single prefixes. This
 | 
						|
 * may do crazy things if a partial reload was to be performed on any other
 | 
						|
 * table than plain IPv6 or IPv4. Network types like VPNv6 or Flowspec may
 | 
						|
 * cause some crashes. This is currently not checked anywhere.
 | 
						|
 *
 | 
						|
 * Anyway, we decided to split the table FIB structure to carry only a mapping
 | 
						|
 * between a prefix and a locally-unique ID, and after this update is done
 | 
						|
 * (probably also in v2), the tracking tries may be easily replaced by
 | 
						|
 * bitfields, therefore fixing this bug.
 | 
						|
 *
 | 
						|
 * We also probably didn't do a proper analysis of the implemented algorithm
 | 
						|
 * for reexports, so if there is somebody willing to formally prove that we
 | 
						|
 * both won't miss any update and won't reexport more than needed, you're welcome
 | 
						|
 * to submit such a proof.
 | 
						|
 *
 | 
						|
 * We wish you a pleasant reading, analyzing and bugfixing experience.
 | 
						|
 *
 | 
						|
 *					  Kata, Maria and the BIRD Team
 | 
						|
 */
 | 
						|
 | 
						|
struct roa_subscription {
 | 
						|
  node roa_node;
 | 
						|
  struct settle settle;
 | 
						|
  struct channel *c;
 | 
						|
  rtable *tab;
 | 
						|
  struct rt_export_request req;
 | 
						|
  struct f_trie *trie;
 | 
						|
};
 | 
						|
 | 
						|
static void
 | 
						|
channel_roa_in_reload_done(struct channel_import_request *req)
 | 
						|
{
 | 
						|
  rfree(req->trie->lp);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_roa_in_changed(struct settle *se)
 | 
						|
{
 | 
						|
  struct roa_subscription *s = SKIP_BACK(struct roa_subscription, settle, se);
 | 
						|
  struct channel *c = s->c;
 | 
						|
 | 
						|
  CD(c, "Reload triggered by RPKI change");
 | 
						|
  struct channel_import_request *cir = lp_alloc(s->trie->lp, sizeof *cir);
 | 
						|
  *cir = (struct channel_import_request) {
 | 
						|
    .trie = s->trie,
 | 
						|
    .done = channel_roa_in_reload_done,
 | 
						|
  };
 | 
						|
 | 
						|
  s->trie = f_new_trie(lp_new(c->proto->pool), 0);
 | 
						|
 | 
						|
  channel_request_partial_reload(c, cir);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_roa_out_reload_done(struct channel_feeding_request *req)
 | 
						|
{
 | 
						|
  rfree(req->trie->lp);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_roa_out_changed(struct settle *se)
 | 
						|
{
 | 
						|
  struct roa_subscription *s = SKIP_BACK(struct roa_subscription, settle, se);
 | 
						|
  struct channel *c = s->c;
 | 
						|
 | 
						|
  CD(c, "Feeding triggered by RPKI change");
 | 
						|
 | 
						|
  /* Setup feeding request */
 | 
						|
  struct channel_feeding_request *cfr = lp_alloc(s->trie->lp, sizeof *cfr);
 | 
						|
  *cfr = (struct channel_feeding_request) {
 | 
						|
    .type = CFRT_AUXILIARY,
 | 
						|
    .trie = s->trie,
 | 
						|
    .done = channel_roa_out_reload_done,
 | 
						|
  };
 | 
						|
 | 
						|
  /* Prepare new trie */
 | 
						|
  s->trie = f_new_trie(lp_new(c->proto->pool), 0);
 | 
						|
 | 
						|
  /* Actually request the feed */
 | 
						|
  channel_request_feeding(c, cfr);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_export_one_roa(struct rt_export_request *req, const net_addr *net, struct rt_pending_export *first)
 | 
						|
{
 | 
						|
  struct roa_subscription *s = SKIP_BACK(struct roa_subscription, req, req);
 | 
						|
 | 
						|
  switch (net->type)
 | 
						|
  {
 | 
						|
    case NET_ROA4:
 | 
						|
      trie_add_prefix(s->trie, net, net_pxlen(net), 32);
 | 
						|
      break;
 | 
						|
    case NET_ROA6:
 | 
						|
      trie_add_prefix(s->trie, net, net_pxlen(net), 128);
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      bug("ROA table sent us a non-roa export");
 | 
						|
  }
 | 
						|
 | 
						|
  settle_kick(&s->settle, s->c->proto->loop);
 | 
						|
 | 
						|
  rpe_mark_seen_all(req->hook, first, NULL, NULL);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_dump_roa_req(struct rt_export_request *req)
 | 
						|
{
 | 
						|
  struct roa_subscription *s = SKIP_BACK(struct roa_subscription, req, req);
 | 
						|
  struct channel *c = s->c;
 | 
						|
 | 
						|
  debug("  Channel %s.%s ROA %s change notifier request %p\n",
 | 
						|
      c->proto->name, c->name,
 | 
						|
      (s->settle.hook == channel_roa_in_changed) ? "import" : "export",
 | 
						|
      req);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
channel_roa_is_subscribed(struct channel *c, rtable *tab, int dir)
 | 
						|
{
 | 
						|
  void (*hook)(struct settle *) =
 | 
						|
    dir ? channel_roa_in_changed : channel_roa_out_changed;
 | 
						|
 | 
						|
  struct roa_subscription *s;
 | 
						|
  node *n;
 | 
						|
 | 
						|
  WALK_LIST2(s, n, c->roa_subscriptions, roa_node)
 | 
						|
    if ((tab == s->tab) && (s->settle.hook == hook))
 | 
						|
      return 1;
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_roa_subscribe(struct channel *c, rtable *tab, int dir)
 | 
						|
{
 | 
						|
  if (channel_roa_is_subscribed(c, tab, dir))
 | 
						|
    return;
 | 
						|
 | 
						|
  struct roa_subscription *s = mb_allocz(c->proto->pool, sizeof(struct roa_subscription));
 | 
						|
 | 
						|
  *s = (struct roa_subscription) {
 | 
						|
    .settle = SETTLE_INIT(&c->roa_settle, dir ? channel_roa_in_changed : channel_roa_out_changed, NULL),
 | 
						|
    .c = c,
 | 
						|
    .trie = f_new_trie(lp_new(c->proto->pool), 0),
 | 
						|
    .tab = tab,
 | 
						|
    .req = {
 | 
						|
      .name = mb_sprintf(c->proto->pool, "%s.%s.roa-%s.%s",
 | 
						|
	  c->proto->name, c->name, dir ? "in" : "out", tab->name),
 | 
						|
      .list = proto_work_list(c->proto),
 | 
						|
      .pool = c->proto->pool,
 | 
						|
      .trace_routes = c->debug | c->proto->debug,
 | 
						|
      .dump_req = channel_dump_roa_req,
 | 
						|
      .export_one = channel_export_one_roa,
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  add_tail(&c->roa_subscriptions, &s->roa_node);
 | 
						|
  rt_request_export(tab, &s->req);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_roa_unsubscribed(struct rt_export_request *req)
 | 
						|
{
 | 
						|
  struct roa_subscription *s = SKIP_BACK(struct roa_subscription, req, req);
 | 
						|
  struct channel *c = s->c;
 | 
						|
 | 
						|
  rem_node(&s->roa_node);
 | 
						|
  mb_free(s);
 | 
						|
  
 | 
						|
  channel_check_stopped(c);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_roa_unsubscribe(struct roa_subscription *s)
 | 
						|
{
 | 
						|
  rfree(s->trie->lp);
 | 
						|
  rt_stop_export(&s->req, channel_roa_unsubscribed);
 | 
						|
  settle_cancel(&s->settle);
 | 
						|
  s->settle.hook = NULL;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_roa_subscribe_filter(struct channel *c, int dir)
 | 
						|
{
 | 
						|
  const struct filter *f = dir ? c->in_filter : c->out_filter;
 | 
						|
  rtable *tab;
 | 
						|
  int valid = 1, found = 0;
 | 
						|
 | 
						|
  if ((f == FILTER_ACCEPT) || (f == FILTER_REJECT))
 | 
						|
    return;
 | 
						|
 | 
						|
  /* No automatic reload for non-reloadable channels */
 | 
						|
  if (dir && !channel_reloadable(c))
 | 
						|
    valid = 0;
 | 
						|
 | 
						|
  struct filter_iterator fit;
 | 
						|
  FILTER_ITERATE_INIT(&fit, f->root, c->proto->pool);
 | 
						|
 | 
						|
  FILTER_ITERATE(&fit, fi)
 | 
						|
  {
 | 
						|
    switch (fi->fi_code)
 | 
						|
    {
 | 
						|
    case FI_ROA_CHECK:
 | 
						|
      tab = fi->i_FI_ROA_CHECK.rtc->table;
 | 
						|
      if (valid) channel_roa_subscribe(c, tab, dir);
 | 
						|
      found = 1;
 | 
						|
      break;
 | 
						|
 | 
						|
    default:
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  FILTER_ITERATE_END;
 | 
						|
 | 
						|
  FILTER_ITERATE_CLEANUP(&fit);
 | 
						|
 | 
						|
  if (!valid && found)
 | 
						|
    log(L_WARN "%s.%s: Automatic RPKI reload not active for %s",
 | 
						|
	c->proto->name, c->name ?: "?", dir ? "import" : "export");
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_roa_unsubscribe_all(struct channel *c)
 | 
						|
{
 | 
						|
  struct roa_subscription *s;
 | 
						|
  node *n, *x;
 | 
						|
 | 
						|
  WALK_LIST2_DELSAFE(s, n, x, c->roa_subscriptions, roa_node)
 | 
						|
    channel_roa_unsubscribe(s);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_start_import(struct channel *c)
 | 
						|
{
 | 
						|
  if (c->in_req.hook)
 | 
						|
  {
 | 
						|
    log(L_WARN "%s.%s: Attempted to start channel's already started import", c->proto->name, c->name);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  c->in_req = (struct rt_import_request) {
 | 
						|
    .name = mb_sprintf(c->proto->pool, "%s.%s", c->proto->name, c->name),
 | 
						|
    .trace_routes = c->debug | c->proto->debug,
 | 
						|
    .loop = c->proto->loop,
 | 
						|
    .dump_req = channel_dump_import_req,
 | 
						|
    .log_state_change = channel_import_log_state_change,
 | 
						|
    .preimport = channel_preimport,
 | 
						|
  };
 | 
						|
 | 
						|
  ASSERT(c->channel_state == CS_UP);
 | 
						|
 | 
						|
  channel_reset_limit(c, &c->rx_limit, PLD_RX);
 | 
						|
  channel_reset_limit(c, &c->in_limit, PLD_IN);
 | 
						|
 | 
						|
  memset(&c->import_stats, 0, sizeof(struct channel_import_stats));
 | 
						|
 | 
						|
  DBG("%s.%s: Channel start import req=%p\n", c->proto->name, c->name, &c->in_req);
 | 
						|
  rt_request_import(c->table, &c->in_req);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_start_export(struct channel *c)
 | 
						|
{
 | 
						|
  if (c->out_req.hook)
 | 
						|
  {
 | 
						|
    log(L_WARN "%s.%s: Attempted to start channel's already started export", c->proto->name, c->name);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  ASSERT(c->channel_state == CS_UP);
 | 
						|
 | 
						|
  pool *p = rp_newf(c->proto->pool, c->proto->pool->domain, "Channel %s.%s export", c->proto->name, c->name);
 | 
						|
 | 
						|
  c->out_req = (struct rt_export_request) {
 | 
						|
    .name = mb_sprintf(p, "%s.%s", c->proto->name, c->name),
 | 
						|
    .list = proto_work_list(c->proto),
 | 
						|
    .pool = p,
 | 
						|
    .feed_block_size = c->feed_block_size,
 | 
						|
    .prefilter = {
 | 
						|
      .mode = c->out_subprefix ? TE_ADDR_IN : TE_ADDR_NONE,
 | 
						|
      .addr = c->out_subprefix,
 | 
						|
    },
 | 
						|
    .trace_routes = c->debug | c->proto->debug,
 | 
						|
    .dump_req = channel_dump_export_req,
 | 
						|
    .log_state_change = channel_export_log_state_change,
 | 
						|
    .mark_seen = channel_rpe_mark_seen_export,
 | 
						|
  };
 | 
						|
 | 
						|
  bmap_init(&c->export_map, p, 16);
 | 
						|
  bmap_init(&c->export_reject_map, p, 16);
 | 
						|
  bmap_init(&c->refeed_map, p, 16);
 | 
						|
 | 
						|
  channel_reset_limit(c, &c->out_limit, PLD_OUT);
 | 
						|
 | 
						|
  memset(&c->export_stats, 0, sizeof(struct channel_export_stats));
 | 
						|
 | 
						|
  switch (c->ra_mode) {
 | 
						|
    case RA_OPTIMAL:
 | 
						|
      c->out_req.export_one = rt_notify_optimal;
 | 
						|
      break;
 | 
						|
    case RA_ANY:
 | 
						|
      c->out_req.export_one = rt_notify_any;
 | 
						|
      c->out_req.export_bulk = rt_feed_any;
 | 
						|
      break;
 | 
						|
    case RA_ACCEPTED:
 | 
						|
      c->out_req.export_bulk = rt_notify_accepted;
 | 
						|
      break;
 | 
						|
    case RA_MERGED:
 | 
						|
      c->out_req.export_bulk = rt_notify_merged;
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      bug("Unknown route announcement mode");
 | 
						|
  }
 | 
						|
 | 
						|
  c->refeed_req = c->out_req;
 | 
						|
  c->refeed_req.pool = rp_newf(c->proto->pool, c->proto->pool->domain, "Channel %s.%s export refeed", c->proto->name, c->name);
 | 
						|
  c->refeed_req.name = mb_sprintf(c->refeed_req.pool, "%s.%s.refeed", c->proto->name, c->name);
 | 
						|
  c->refeed_req.dump_req = channel_dump_refeed_req;
 | 
						|
  c->refeed_req.log_state_change = channel_refeed_log_state_change;
 | 
						|
  c->refeed_req.mark_seen = channel_rpe_mark_seen_refeed;
 | 
						|
 | 
						|
  DBG("%s.%s: Channel start export req=%p\n", c->proto->name, c->name, &c->out_req);
 | 
						|
  rt_request_export(c->table, &c->out_req);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_check_stopped(struct channel *c)
 | 
						|
{
 | 
						|
  switch (c->channel_state)
 | 
						|
  {
 | 
						|
    case CS_STOP:
 | 
						|
      if (c->obstacles || !EMPTY_LIST(c->roa_subscriptions) || c->out_req.hook || c->refeed_req.hook || c->in_req.hook || c->reload_req.hook)
 | 
						|
	return;
 | 
						|
 | 
						|
      channel_set_state(c, CS_DOWN);
 | 
						|
      proto_send_event(c->proto, c->proto->event);
 | 
						|
 | 
						|
      break;
 | 
						|
    case CS_PAUSE:
 | 
						|
      if (c->obstacles || !EMPTY_LIST(c->roa_subscriptions) || c->out_req.hook || c->refeed_req.hook || c->reload_req.hook)
 | 
						|
	return;
 | 
						|
 | 
						|
      channel_set_state(c, CS_START);
 | 
						|
      break;
 | 
						|
  }
 | 
						|
 | 
						|
  DBG("%s.%s: Channel requests/hooks stopped (in state %s)\n", c->proto->name, c->name, c_states[c->channel_state]);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
channel_add_obstacle(struct channel *c)
 | 
						|
{
 | 
						|
  c->obstacles++;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
channel_del_obstacle(struct channel *c)
 | 
						|
{
 | 
						|
  if (!--c->obstacles)
 | 
						|
    channel_check_stopped(c);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
channel_import_stopped(struct rt_import_request *req)
 | 
						|
{
 | 
						|
  struct channel *c = SKIP_BACK(struct channel, in_req, req);
 | 
						|
 | 
						|
  mb_free(c->in_req.name);
 | 
						|
  c->in_req.name = NULL;
 | 
						|
 | 
						|
  channel_check_stopped(c);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_export_stopped(struct rt_export_request *req)
 | 
						|
{
 | 
						|
  struct channel *c = SKIP_BACK(struct channel, out_req, req);
 | 
						|
 | 
						|
  /* The hook has already stopped */
 | 
						|
  req->hook = NULL;
 | 
						|
 | 
						|
  if (c->refeed_pending)
 | 
						|
  {
 | 
						|
    ASSERT_DIE(!c->refeeding);
 | 
						|
    c->refeeding = c->refeed_pending;
 | 
						|
    c->refeed_pending = NULL;
 | 
						|
 | 
						|
    channel_reset_limit(c, &c->out_limit, PLD_OUT);
 | 
						|
 | 
						|
    bmap_reset(&c->export_map, 16);
 | 
						|
    bmap_reset(&c->export_reject_map, 16);
 | 
						|
    bmap_reset(&c->refeed_map, 16);
 | 
						|
 | 
						|
    rt_request_export(c->table, req);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  bmap_free(&c->export_map);
 | 
						|
  bmap_free(&c->export_reject_map);
 | 
						|
 | 
						|
  c->out_req.name = NULL;
 | 
						|
  rfree(c->out_req.pool);
 | 
						|
 | 
						|
  channel_check_stopped(c);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_refeed_stopped(struct rt_export_request *req)
 | 
						|
{
 | 
						|
  struct channel *c = SKIP_BACK(struct channel, refeed_req, req);
 | 
						|
 | 
						|
  req->hook = NULL;
 | 
						|
 | 
						|
  channel_feed_end(c);
 | 
						|
  channel_check_stopped(c);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_init_feeding(struct channel *c)
 | 
						|
{
 | 
						|
  int no_trie = 0;
 | 
						|
 | 
						|
  for (struct channel_feeding_request *cfrp = c->refeed_pending; cfrp; cfrp = cfrp->next)
 | 
						|
    if (cfrp->type == CFRT_DIRECT)
 | 
						|
    {
 | 
						|
      /* Direct feeding requested? Restart the export by force. */
 | 
						|
      channel_stop_export(c);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    else if (!cfrp->trie)
 | 
						|
      no_trie = 1;
 | 
						|
 | 
						|
  /* No direct feeding, running auxiliary refeed. */
 | 
						|
  c->refeeding = c->refeed_pending;
 | 
						|
  c->refeed_pending = NULL;
 | 
						|
  bmap_reset(&c->refeed_map, 16);
 | 
						|
 | 
						|
  if (no_trie)
 | 
						|
  {
 | 
						|
    c->refeed_req.prefilter.mode = TE_ADDR_NONE;
 | 
						|
    c->refeed_req.prefilter.hook = NULL;
 | 
						|
  }
 | 
						|
  else
 | 
						|
  {
 | 
						|
    c->refeed_req.prefilter.mode = TE_ADDR_HOOK;
 | 
						|
    c->refeed_req.prefilter.hook = channel_refeed_prefilter;
 | 
						|
  }
 | 
						|
 | 
						|
  rt_request_export(c->table, &c->refeed_req);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
channel_refeed_prefilter(const struct rt_prefilter *p, const net_addr *n)
 | 
						|
{
 | 
						|
  const struct channel *c =
 | 
						|
    SKIP_BACK(struct channel, refeed_req,
 | 
						|
	SKIP_BACK(struct rt_export_request, prefilter, p)
 | 
						|
	);
 | 
						|
 | 
						|
  ASSERT_DIE(c->refeeding);
 | 
						|
  for (struct channel_feeding_request *cfr = c->refeeding; cfr; cfr = cfr->next)
 | 
						|
    if (!cfr->trie || trie_match_net(cfr->trie, n))
 | 
						|
      return 1;
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
channel_import_request_prefilter(struct channel_import_request *cir_head, const net_addr *n)
 | 
						|
{
 | 
						|
  for (struct channel_import_request *cir = cir_head; cir; cir = cir->next)
 | 
						|
  {
 | 
						|
    if (!cir->trie || trie_match_net(cir->trie, n))
 | 
						|
      return 1;
 | 
						|
  }
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
channel_import_prefilter(const struct rt_prefilter *p, const net_addr *n)
 | 
						|
{
 | 
						|
  const struct channel *c =
 | 
						|
    SKIP_BACK(struct channel, reload_req,
 | 
						|
	SKIP_BACK(struct rt_export_request, prefilter, p)
 | 
						|
	);
 | 
						|
  ASSERT_DIE(c->importing);
 | 
						|
 | 
						|
  return channel_import_request_prefilter(c->importing, n);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_feed_end(struct channel *c)
 | 
						|
{
 | 
						|
  /* Reset export limit if the feed ended with acceptable number of exported routes */
 | 
						|
  struct limit *l = &c->out_limit;
 | 
						|
  if (c->refeeding &&
 | 
						|
      (c->limit_active & (1 << PLD_OUT)) &&
 | 
						|
      (l->count <= l->max))
 | 
						|
  {
 | 
						|
    log(L_INFO "Protocol %s resets route export limit (%u)", c->proto->name, l->max);
 | 
						|
    c->limit_active &= ~(1 << PLD_OUT);
 | 
						|
 | 
						|
    /* Queue the same refeed batch back into pending */
 | 
						|
    struct channel_feeding_request **ptr = &c->refeed_pending;
 | 
						|
    while (*ptr)
 | 
						|
      ptr = &((*ptr)->next);
 | 
						|
 | 
						|
    *ptr = c->refeeding;
 | 
						|
 | 
						|
    /* Mark the requests to be redone */
 | 
						|
    for (struct channel_feeding_request *cfr = c->refeeding; cfr; cfr = cfr->next)
 | 
						|
      cfr->state = CFRS_PENDING;
 | 
						|
 | 
						|
    c->refeeding = NULL;
 | 
						|
  }
 | 
						|
 | 
						|
  /* Inform the protocol about the feed ending */
 | 
						|
  CALL(c->proto->feed_end, c);
 | 
						|
 | 
						|
  /* Free the dynamic feeding requests */
 | 
						|
  for (struct channel_feeding_request *cfr = c->refeeding, *next = cfr ? cfr->next : NULL;
 | 
						|
      cfr;
 | 
						|
      (cfr = next), (next = next ? next->next : NULL))
 | 
						|
    CALL(cfr->done, cfr);
 | 
						|
 | 
						|
  /* Drop the refeed batch */
 | 
						|
  c->refeeding = NULL;
 | 
						|
 | 
						|
  /* Run the pending batch */
 | 
						|
  if (c->refeed_pending)
 | 
						|
    channel_init_feeding(c);
 | 
						|
}
 | 
						|
 | 
						|
/* Called by protocol for reload from in_table */
 | 
						|
void
 | 
						|
channel_schedule_reload(struct channel *c, struct channel_import_request *cir)
 | 
						|
{
 | 
						|
  ASSERT(c->in_req.hook);
 | 
						|
  int no_trie = 0;
 | 
						|
  if (cir)
 | 
						|
  {
 | 
						|
    cir->next = c->import_pending;
 | 
						|
    c->import_pending = cir;
 | 
						|
  }
 | 
						|
 | 
						|
  if (c->reload_req.hook)
 | 
						|
  {
 | 
						|
    CD(c, "Reload triggered before the previous one has finished");
 | 
						|
    c->reload_pending = 1;
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  /* If there is any full-reload request, we can disregard all partials */
 | 
						|
  for (struct channel_import_request *last = cir; last && no_trie==0;)
 | 
						|
  {
 | 
						|
    if (!last->trie)
 | 
						|
      no_trie = 1;
 | 
						|
     last = last->next;
 | 
						|
  }
 | 
						|
 | 
						|
  /* activating pending imports */
 | 
						|
  c->importing = c->import_pending;
 | 
						|
  c->import_pending = NULL;
 | 
						|
 | 
						|
  if (no_trie)
 | 
						|
  {
 | 
						|
    c->reload_req.prefilter.mode = TE_ADDR_NONE;
 | 
						|
    c->reload_req.prefilter.hook = NULL;
 | 
						|
  }
 | 
						|
  else
 | 
						|
  {
 | 
						|
    c->reload_req.prefilter.mode = TE_ADDR_HOOK;
 | 
						|
    c->reload_req.prefilter.hook = channel_import_prefilter;
 | 
						|
  }
 | 
						|
 | 
						|
  rt_request_export(c->table, &c->reload_req);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_reload_stopped(struct rt_export_request *req)
 | 
						|
{
 | 
						|
  struct channel *c = SKIP_BACK(struct channel, reload_req, req);
 | 
						|
 | 
						|
  req->hook = NULL;
 | 
						|
 | 
						|
  /* Restart reload */
 | 
						|
  if (c->reload_pending)
 | 
						|
  {
 | 
						|
    c->reload_pending = 0;
 | 
						|
    channel_request_reload(c);
 | 
						|
  }
 | 
						|
 | 
						|
  if (c->channel_state != CS_UP)
 | 
						|
    channel_check_stopped(c);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_reload_log_state_change(struct rt_export_request *req, u8 state)
 | 
						|
{
 | 
						|
  struct channel *c = SKIP_BACK(struct channel, reload_req, req);
 | 
						|
 | 
						|
  if (state == TES_READY)
 | 
						|
  {
 | 
						|
    if (c->channel_state == CS_UP)
 | 
						|
      rt_refresh_end(&c->in_req);
 | 
						|
 | 
						|
    rt_stop_export(req, channel_reload_stopped);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_reload_dump_req(struct rt_export_request *req)
 | 
						|
{
 | 
						|
  struct channel *c = SKIP_BACK(struct channel, reload_req, req);
 | 
						|
  debug("  Channel %s.%s import reload request %p\n", c->proto->name, c->name, req);
 | 
						|
}
 | 
						|
 | 
						|
/* Called by protocol to activate in_table */
 | 
						|
static void
 | 
						|
channel_setup_in_table(struct channel *c)
 | 
						|
{
 | 
						|
  c->reload_req = (struct rt_export_request) {
 | 
						|
    .name = mb_sprintf(c->proto->pool, "%s.%s.import", c->proto->name, c->name),
 | 
						|
    .list = proto_work_list(c->proto),
 | 
						|
    .pool = c->proto->pool,
 | 
						|
    .feed_block_size = c->feed_block_size,
 | 
						|
    .trace_routes = c->debug | c->proto->debug,
 | 
						|
    .export_bulk = channel_reload_export_bulk,
 | 
						|
    .dump_req = channel_reload_dump_req,
 | 
						|
    .log_state_change = channel_reload_log_state_change,
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
channel_do_start(struct channel *c)
 | 
						|
{
 | 
						|
  c->proto->active_channels++;
 | 
						|
 | 
						|
  if ((c->in_keep & RIK_PREFILTER) == RIK_PREFILTER)
 | 
						|
    channel_setup_in_table(c);
 | 
						|
 | 
						|
  CALL(c->class->start, c);
 | 
						|
 | 
						|
  channel_start_import(c);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_do_up(struct channel *c)
 | 
						|
{
 | 
						|
  /* Register RPKI/ROA subscriptions */
 | 
						|
  if (c->rpki_reload)
 | 
						|
  {
 | 
						|
    channel_roa_subscribe_filter(c, 1);
 | 
						|
    channel_roa_subscribe_filter(c, 0);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_do_pause(struct channel *c)
 | 
						|
{
 | 
						|
  /* Drop ROA subscriptions */
 | 
						|
  channel_roa_unsubscribe_all(c);
 | 
						|
 | 
						|
  /* Need to abort feeding */
 | 
						|
  c->reload_pending = 0;
 | 
						|
 | 
						|
  if (c->reload_req.hook && atomic_load_explicit(&c->reload_req.hook->export_state, memory_order_acquire) != TES_STOP)
 | 
						|
    rt_stop_export(&c->reload_req, channel_reload_stopped);
 | 
						|
 | 
						|
  /* Stop export */
 | 
						|
  c->refeed_pending = 0;
 | 
						|
  channel_stop_export(c);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_do_stop(struct channel *c)
 | 
						|
{
 | 
						|
  /* Stop import */
 | 
						|
  if (c->in_req.hook)
 | 
						|
    rt_stop_import(&c->in_req, channel_import_stopped);
 | 
						|
 | 
						|
  c->gr_wait = 0;
 | 
						|
  if (c->gr_lock)
 | 
						|
    channel_graceful_restart_unlock(c);
 | 
						|
 | 
						|
  CALL(c->class->shutdown, c);
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_do_down(struct channel *c)
 | 
						|
{
 | 
						|
  ASSERT(!c->reload_req.hook);
 | 
						|
 | 
						|
  c->proto->active_channels--;
 | 
						|
 | 
						|
  memset(&c->import_stats, 0, sizeof(struct channel_import_stats));
 | 
						|
  memset(&c->export_stats, 0, sizeof(struct channel_export_stats));
 | 
						|
 | 
						|
  /* The in_table and out_table are going to be freed by freeing their resource pools. */
 | 
						|
 | 
						|
  CALL(c->class->cleanup, c);
 | 
						|
 | 
						|
  /* Schedule protocol shutddown */
 | 
						|
  if (proto_is_done(c->proto))
 | 
						|
    proto_send_event(c->proto, c->proto->event);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
channel_set_state(struct channel *c, uint state)
 | 
						|
{
 | 
						|
  uint cs = c->channel_state;
 | 
						|
 | 
						|
  DBG("%s reporting channel %s state transition %s -> %s\n", c->proto->name, c->name, c_states[cs], c_states[state]);
 | 
						|
  if (state == cs)
 | 
						|
    return;
 | 
						|
 | 
						|
  c->channel_state = state;
 | 
						|
  c->last_state_change = current_time();
 | 
						|
 | 
						|
  switch (state)
 | 
						|
  {
 | 
						|
  case CS_START:
 | 
						|
    ASSERT(cs == CS_DOWN || cs == CS_PAUSE);
 | 
						|
 | 
						|
    if (cs == CS_DOWN)
 | 
						|
      channel_do_start(c);
 | 
						|
 | 
						|
    break;
 | 
						|
 | 
						|
  case CS_UP:
 | 
						|
    ASSERT(cs == CS_DOWN || cs == CS_START);
 | 
						|
 | 
						|
    if (cs == CS_DOWN)
 | 
						|
      channel_do_start(c);
 | 
						|
 | 
						|
    if (!c->gr_wait && c->proto->rt_notify)
 | 
						|
      channel_start_export(c);
 | 
						|
 | 
						|
    channel_do_up(c);
 | 
						|
    break;
 | 
						|
 | 
						|
  case CS_PAUSE:
 | 
						|
    ASSERT(cs == CS_UP);
 | 
						|
 | 
						|
    if (cs == CS_UP)
 | 
						|
      channel_do_pause(c);
 | 
						|
    break;
 | 
						|
 | 
						|
  case CS_STOP:
 | 
						|
    ASSERT(cs == CS_UP || cs == CS_START || cs == CS_PAUSE);
 | 
						|
 | 
						|
    if (cs == CS_UP)
 | 
						|
      channel_do_pause(c);
 | 
						|
 | 
						|
    channel_do_stop(c);
 | 
						|
    break;
 | 
						|
 | 
						|
  case CS_DOWN:
 | 
						|
    ASSERT(cs == CS_STOP);
 | 
						|
 | 
						|
    channel_do_down(c);
 | 
						|
    break;
 | 
						|
 | 
						|
  default:
 | 
						|
    ASSERT(0);
 | 
						|
  }
 | 
						|
 | 
						|
  channel_log_state_change(c);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * channel_request_feeding - request feeding routes to the channel
 | 
						|
 * @c: given channel
 | 
						|
 *
 | 
						|
 * Sometimes it is needed to send again all routes to the channel. This is
 | 
						|
 * called feeding and can be requested by this function. This would cause
 | 
						|
 * channel export state transition to ES_FEEDING (during feeding) and when
 | 
						|
 * completed, it will switch back to ES_READY. This function can be called
 | 
						|
 * even when feeding is already running, in that case it is restarted.
 | 
						|
 */
 | 
						|
void
 | 
						|
channel_request_feeding(struct channel *c, struct channel_feeding_request *cfr)
 | 
						|
{
 | 
						|
  CD(c, "Feeding requested (%s)",
 | 
						|
      cfr->type == CFRT_DIRECT ? "direct" :
 | 
						|
      (cfr->trie ? "partial" : "auxiliary"));
 | 
						|
 | 
						|
  /* Enqueue the request */
 | 
						|
  cfr->next = c->refeed_pending;
 | 
						|
  c->refeed_pending = cfr;
 | 
						|
 | 
						|
  /* Initialize refeeds unless already refeeding */
 | 
						|
  if (!c->refeeding)
 | 
						|
    channel_init_feeding(c);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_feeding_request_done_dynamic(struct channel_feeding_request *req)
 | 
						|
{
 | 
						|
  mb_free(req);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
channel_request_feeding_dynamic(struct channel *c, enum channel_feeding_request_type type)
 | 
						|
{
 | 
						|
  struct channel_feeding_request *req = mb_alloc(c->proto->pool, sizeof *req);
 | 
						|
  *req = (struct channel_feeding_request) {
 | 
						|
    .type = type,
 | 
						|
    .done = channel_feeding_request_done_dynamic,
 | 
						|
  };
 | 
						|
 | 
						|
  channel_request_feeding(c, req);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_stop_export(struct channel *c)
 | 
						|
{
 | 
						|
  if (c->refeed_req.hook && (atomic_load_explicit(&c->refeed_req.hook->export_state, memory_order_acquire) != TES_STOP))
 | 
						|
    rt_stop_export(&c->refeed_req, channel_refeed_stopped);
 | 
						|
 | 
						|
  if (c->out_req.hook && (atomic_load_explicit(&c->out_req.hook->export_state, memory_order_acquire) != TES_STOP))
 | 
						|
    rt_stop_export(&c->out_req, channel_export_stopped);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_import_request_done_dynamic(struct channel_import_request *req)
 | 
						|
{
 | 
						|
  mb_free(req);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
channel_request_reload(struct channel *c)
 | 
						|
{
 | 
						|
  ASSERT_DIE(c->in_req.hook);
 | 
						|
  ASSERT_DIE(channel_reloadable(c));
 | 
						|
 | 
						|
  CD(c, "Reload requested");
 | 
						|
  struct channel_import_request* cir = mb_alloc(c->proto->pool, sizeof *cir);
 | 
						|
  cir->trie = NULL;
 | 
						|
  cir->done = channel_import_request_done_dynamic;
 | 
						|
 | 
						|
  if ((c->in_keep & RIK_PREFILTER) == RIK_PREFILTER)
 | 
						|
    return channel_schedule_reload(c, cir);
 | 
						|
  else if (! c->proto->reload_routes(c, cir))
 | 
						|
    bug("Channel %s.%s refused full import reload.", c->proto->name, c->name);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_request_partial_reload(struct channel *c, struct channel_import_request *cir)
 | 
						|
{
 | 
						|
  ASSERT_DIE(c->in_req.hook);
 | 
						|
  ASSERT_DIE(channel_reloadable(c));
 | 
						|
 | 
						|
  CD(c, "Partial import reload requested");
 | 
						|
 | 
						|
  if ((c->in_keep & RIK_PREFILTER) == RIK_PREFILTER)
 | 
						|
    channel_schedule_reload(c, cir);
 | 
						|
  else if (! c->proto->reload_routes(c, cir))
 | 
						|
    cli_msg(-15, "%s.%s: partial reload refused, please run full reload instead", c->proto->name, c->name);
 | 
						|
}
 | 
						|
 | 
						|
const struct channel_class channel_basic = {
 | 
						|
  .channel_size = sizeof(struct channel),
 | 
						|
  .config_size = sizeof(struct channel_config)
 | 
						|
};
 | 
						|
 | 
						|
void *
 | 
						|
channel_config_new(const struct channel_class *cc, const char *name, uint net_type, struct proto_config *proto)
 | 
						|
{
 | 
						|
  struct channel_config *cf = NULL;
 | 
						|
  struct rtable_config *tab = NULL;
 | 
						|
 | 
						|
  if (net_type)
 | 
						|
  {
 | 
						|
    if (!net_val_match(net_type, proto->protocol->channel_mask))
 | 
						|
      cf_error("Unsupported channel type");
 | 
						|
 | 
						|
    if (proto->net_type && (net_type != proto->net_type) && (net_type != NET_MPLS))
 | 
						|
      cf_error("Different channel type");
 | 
						|
 | 
						|
    tab = rt_get_default_table(new_config, net_type);
 | 
						|
  }
 | 
						|
 | 
						|
  if (!cc)
 | 
						|
    cc = &channel_basic;
 | 
						|
 | 
						|
  cf = cfg_allocz(cc->config_size);
 | 
						|
  cf->name = name;
 | 
						|
  cf->class = cc;
 | 
						|
  cf->parent = proto;
 | 
						|
  cf->table = tab;
 | 
						|
  cf->out_filter = FILTER_REJECT;
 | 
						|
 | 
						|
  cf->feed_block_size = 16384;
 | 
						|
 | 
						|
  cf->net_type = net_type;
 | 
						|
  cf->ra_mode = RA_OPTIMAL;
 | 
						|
  cf->preference = proto->protocol->preference;
 | 
						|
  cf->debug = new_config->channel_default_debug;
 | 
						|
  cf->rpki_reload = 1;
 | 
						|
 | 
						|
  cf->roa_settle = (struct settle_config) {
 | 
						|
    .min = 1 S,
 | 
						|
    .max = 20 S,
 | 
						|
  };
 | 
						|
 | 
						|
  add_tail(&proto->channels, &cf->n);
 | 
						|
 | 
						|
  return cf;
 | 
						|
}
 | 
						|
 | 
						|
void *
 | 
						|
channel_config_get(const struct channel_class *cc, const char *name, uint net_type, struct proto_config *proto)
 | 
						|
{
 | 
						|
  struct channel_config *cf;
 | 
						|
 | 
						|
  /* We are using name as token, so no strcmp() */
 | 
						|
  WALK_LIST(cf, proto->channels)
 | 
						|
    if (cf->name == name)
 | 
						|
    {
 | 
						|
      /* Allow to redefine channel only if inherited from template */
 | 
						|
      if (cf->parent == proto)
 | 
						|
	cf_error("Multiple %s channels", name);
 | 
						|
 | 
						|
      cf->parent = proto;
 | 
						|
      cf->copy = 1;
 | 
						|
      return cf;
 | 
						|
    }
 | 
						|
 | 
						|
  return channel_config_new(cc, name, net_type, proto);
 | 
						|
}
 | 
						|
 | 
						|
struct channel_config *
 | 
						|
channel_copy_config(struct channel_config *src, struct proto_config *proto)
 | 
						|
{
 | 
						|
  struct channel_config *dst = cfg_alloc(src->class->config_size);
 | 
						|
 | 
						|
  memcpy(dst, src, src->class->config_size);
 | 
						|
  memset(&dst->n, 0, sizeof(node));
 | 
						|
  add_tail(&proto->channels, &dst->n);
 | 
						|
  CALL(src->class->copy_config, dst, src);
 | 
						|
 | 
						|
  return dst;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int reconfigure_type;  /* Hack to propagate type info to channel_reconfigure() */
 | 
						|
 | 
						|
int
 | 
						|
channel_reconfigure(struct channel *c, struct channel_config *cf)
 | 
						|
{
 | 
						|
  /* Touched by reconfiguration */
 | 
						|
  c->stale = 0;
 | 
						|
 | 
						|
  /* FIXME: better handle these changes, also handle in_keep_filtered */
 | 
						|
  if ((c->table != cf->table->table) ||
 | 
						|
      (cf->ra_mode && (c->ra_mode != cf->ra_mode)) ||
 | 
						|
      (cf->in_keep != c->in_keep) ||
 | 
						|
      cf->out_subprefix && c->out_subprefix &&
 | 
						|
	  !net_equal(cf->out_subprefix, c->out_subprefix) ||
 | 
						|
      (!cf->out_subprefix != !c->out_subprefix))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  /* Note that filter_same() requires arguments in (new, old) order */
 | 
						|
  int import_changed = !filter_same(cf->in_filter, c->in_filter);
 | 
						|
  int export_changed = !filter_same(cf->out_filter, c->out_filter);
 | 
						|
  int rpki_reload_changed = (cf->rpki_reload != c->rpki_reload);
 | 
						|
 | 
						|
  if (c->preference != cf->preference)
 | 
						|
    import_changed = 1;
 | 
						|
 | 
						|
  if (c->merge_limit != cf->merge_limit)
 | 
						|
    export_changed = 1;
 | 
						|
 | 
						|
  /* Reconfigure channel fields */
 | 
						|
  c->in_filter = cf->in_filter;
 | 
						|
  c->out_filter = cf->out_filter;
 | 
						|
 | 
						|
  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;
 | 
						|
  c->preference = cf->preference;
 | 
						|
  c->out_req.prefilter.addr = c->out_subprefix = cf->out_subprefix;
 | 
						|
  c->debug = cf->debug;
 | 
						|
  c->in_req.trace_routes = c->out_req.trace_routes = c->debug | c->proto->debug;
 | 
						|
  c->rpki_reload = cf->rpki_reload;
 | 
						|
 | 
						|
  if (	  (c->roa_settle.min != cf->roa_settle.min)
 | 
						|
       || (c->roa_settle.max != cf->roa_settle.max))
 | 
						|
  {
 | 
						|
    c->roa_settle = cf->roa_settle;
 | 
						|
 | 
						|
    struct roa_subscription *s;
 | 
						|
    node *n;
 | 
						|
 | 
						|
    WALK_LIST2(s, n, c->roa_subscriptions, roa_node)
 | 
						|
    {
 | 
						|
      s->settle.cf = cf->roa_settle;
 | 
						|
      if (settle_active(&s->settle))
 | 
						|
	settle_kick(&s->settle, &main_birdloop);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* Execute channel-specific reconfigure hook */
 | 
						|
  if (c->class->reconfigure && !c->class->reconfigure(c, cf, &import_changed, &export_changed))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  /* If the channel is not open, it has no routes and we cannot reload it anyways */
 | 
						|
  if (c->channel_state != CS_UP)
 | 
						|
    goto done;
 | 
						|
 | 
						|
  /* Update RPKI/ROA subscriptions */
 | 
						|
  if (import_changed || export_changed || rpki_reload_changed)
 | 
						|
  {
 | 
						|
    channel_roa_unsubscribe_all(c);
 | 
						|
 | 
						|
    if (c->rpki_reload)
 | 
						|
    {
 | 
						|
      channel_roa_subscribe_filter(c, 1);
 | 
						|
      channel_roa_subscribe_filter(c, 0);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (reconfigure_type == RECONFIG_SOFT)
 | 
						|
  {
 | 
						|
    if (import_changed)
 | 
						|
      log(L_INFO "Channel %s.%s changed import", c->proto->name, c->name);
 | 
						|
 | 
						|
    if (export_changed)
 | 
						|
      log(L_INFO "Channel %s.%s changed export", c->proto->name, c->name);
 | 
						|
 | 
						|
    goto done;
 | 
						|
  }
 | 
						|
 | 
						|
  /* Route reload may be not supported */
 | 
						|
  if (import_changed && !channel_reloadable(c))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  if (import_changed || export_changed)
 | 
						|
    log(L_INFO "Reloading channel %s.%s", c->proto->name, c->name);
 | 
						|
 | 
						|
  if (import_changed)
 | 
						|
    channel_request_reload(c);
 | 
						|
 | 
						|
  if (export_changed)
 | 
						|
    channel_request_feeding_dynamic(c, CFRT_AUXILIARY);
 | 
						|
 | 
						|
done:
 | 
						|
  CD(c, "Reconfigured");
 | 
						|
  return 1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
int
 | 
						|
proto_configure_channel(struct proto *p, struct channel **pc, struct channel_config *cf)
 | 
						|
{
 | 
						|
  struct channel *c = *pc;
 | 
						|
 | 
						|
  if (!c && cf)
 | 
						|
  {
 | 
						|
    /* We could add the channel, but currently it would just stay in down state
 | 
						|
       until protocol is restarted, so it is better to force restart anyways. */
 | 
						|
    if (p->proto_state != PS_DOWN)
 | 
						|
    {
 | 
						|
      log(L_INFO "Cannot add channel %s.%s", p->name, cf->name);
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
 | 
						|
    *pc = proto_add_channel(p, cf);
 | 
						|
  }
 | 
						|
  else if (c && !cf)
 | 
						|
  {
 | 
						|
    if (c->channel_state != CS_DOWN)
 | 
						|
    {
 | 
						|
      log(L_INFO "Cannot remove channel %s.%s", c->proto->name, c->name);
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
 | 
						|
    proto_remove_channel(p, c);
 | 
						|
    *pc = NULL;
 | 
						|
  }
 | 
						|
  else if (c && cf)
 | 
						|
  {
 | 
						|
    if (!channel_reconfigure(c, cf))
 | 
						|
    {
 | 
						|
      log(L_INFO "Cannot reconfigure channel %s.%s", c->proto->name, c->name);
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return 1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
proto_cleanup(struct proto *p)
 | 
						|
{
 | 
						|
  CALL(p->proto->cleanup, p);
 | 
						|
 | 
						|
  if (p->pool)
 | 
						|
  {
 | 
						|
    rp_free(p->pool);
 | 
						|
    p->pool = NULL;
 | 
						|
  }
 | 
						|
 | 
						|
  p->active = 0;
 | 
						|
  proto_log_state_change(p);
 | 
						|
 | 
						|
  proto_rethink_goal(p);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_loop_stopped(void *ptr)
 | 
						|
{
 | 
						|
  struct proto *p = ptr;
 | 
						|
 | 
						|
  ASSERT_DIE(birdloop_inside(&main_birdloop));
 | 
						|
  ASSERT_DIE(p->loop != &main_birdloop);
 | 
						|
 | 
						|
  p->pool = NULL; /* is freed by birdloop_free() */
 | 
						|
  birdloop_free(p->loop);
 | 
						|
  p->loop = &main_birdloop;
 | 
						|
 | 
						|
  proto_cleanup(p);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
proto_event(void *ptr)
 | 
						|
{
 | 
						|
  struct proto *p = ptr;
 | 
						|
 | 
						|
  if (p->do_stop)
 | 
						|
  {
 | 
						|
    iface_unsubscribe(&p->iface_sub);
 | 
						|
 | 
						|
    p->do_stop = 0;
 | 
						|
  }
 | 
						|
 | 
						|
  if (proto_is_done(p) && p->pool_inloop)  /* perusing pool_inloop to do this once only */
 | 
						|
  {
 | 
						|
    rp_free(p->pool_inloop);
 | 
						|
    p->pool_inloop = NULL;
 | 
						|
    if (p->loop != &main_birdloop)
 | 
						|
      birdloop_stop_self(p->loop, proto_loop_stopped, p);
 | 
						|
    else
 | 
						|
      proto_cleanup(p);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * proto_new - create a new protocol instance
 | 
						|
 * @c: protocol configuration
 | 
						|
 *
 | 
						|
 * When a new configuration has been read in, the core code starts
 | 
						|
 * initializing all the protocol instances configured by calling their
 | 
						|
 * init() hooks with the corresponding instance configuration. The initialization
 | 
						|
 * code of the protocol is expected to create a new instance according to the
 | 
						|
 * configuration by calling this function and then modifying the default settings
 | 
						|
 * to values wanted by the protocol.
 | 
						|
 */
 | 
						|
void *
 | 
						|
proto_new(struct proto_config *cf)
 | 
						|
{
 | 
						|
  struct proto *p = mb_allocz(proto_pool, cf->protocol->proto_size);
 | 
						|
 | 
						|
  p->cf = cf;
 | 
						|
  p->debug = cf->debug;
 | 
						|
  p->mrtdump = cf->mrtdump;
 | 
						|
  p->name = cf->name;
 | 
						|
  p->proto = cf->protocol;
 | 
						|
  p->net_type = cf->net_type;
 | 
						|
  p->disabled = cf->disabled;
 | 
						|
  p->hash_key = random_u32();
 | 
						|
  cf->proto = p;
 | 
						|
 | 
						|
  init_list(&p->channels);
 | 
						|
 | 
						|
  return p;
 | 
						|
}
 | 
						|
 | 
						|
static struct proto *
 | 
						|
proto_init(struct proto_config *c, struct proto *after)
 | 
						|
{
 | 
						|
  struct protocol *pr = c->protocol;
 | 
						|
  struct proto *p = pr->init(c);
 | 
						|
 | 
						|
  p->loop = &main_birdloop;
 | 
						|
  p->proto_state = PS_DOWN;
 | 
						|
  p->last_state_change = current_time();
 | 
						|
  p->vrf = c->vrf;
 | 
						|
  proto_add_after(&global_proto_list, p, after);
 | 
						|
 | 
						|
  p->event = ev_new_init(proto_pool, proto_event, p);
 | 
						|
 | 
						|
  PD(p, "Initializing%s", p->disabled ? " [disabled]" : "");
 | 
						|
 | 
						|
  return p;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_start(struct proto *p)
 | 
						|
{
 | 
						|
  DBG("Kicking %s up\n", p->name);
 | 
						|
  PD(p, "Starting");
 | 
						|
 | 
						|
  if (graceful_restart_state == GRS_INIT)
 | 
						|
    p->gr_recovery = 1;
 | 
						|
 | 
						|
  if (p->cf->loop_order != DOMAIN_ORDER(the_bird))
 | 
						|
  {
 | 
						|
    p->loop = birdloop_new(proto_pool, p->cf->loop_order, p->cf->loop_max_latency, "Protocol %s", p->cf->name);
 | 
						|
    p->pool = birdloop_pool(p->loop);
 | 
						|
  }
 | 
						|
  else
 | 
						|
    p->pool = rp_newf(proto_pool, the_bird_domain.the_bird, "Protocol %s", p->cf->name);
 | 
						|
 | 
						|
  p->iface_sub.target = proto_event_list(p);
 | 
						|
  p->iface_sub.name = p->name;
 | 
						|
  p->iface_sub.debug = !!(p->debug & D_IFACES);
 | 
						|
 | 
						|
  PROTO_LOCKED_FROM_MAIN(p)
 | 
						|
  {
 | 
						|
    p->pool_inloop = rp_newf(p->pool, birdloop_domain(p->loop), "Protocol %s early cleanup objects", p->cf->name);
 | 
						|
    p->pool_up = rp_newf(p->pool, birdloop_domain(p->loop), "Protocol %s stop-free objects", p->cf->name);
 | 
						|
    proto_notify_state(p, (p->proto->start ? p->proto->start(p) : PS_UP));
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * proto_config_new - create a new protocol configuration
 | 
						|
 * @pr: protocol the configuration will belong to
 | 
						|
 * @class: SYM_PROTO or SYM_TEMPLATE
 | 
						|
 *
 | 
						|
 * Whenever the configuration file says that a new instance
 | 
						|
 * of a routing protocol should be created, the parser calls
 | 
						|
 * proto_config_new() to create a configuration entry for this
 | 
						|
 * instance (a structure staring with the &proto_config header
 | 
						|
 * containing all the generic items followed by protocol-specific
 | 
						|
 * ones). Also, the configuration entry gets added to the list
 | 
						|
 * of protocol instances kept in the configuration.
 | 
						|
 *
 | 
						|
 * The function is also used to create protocol templates (when class
 | 
						|
 * SYM_TEMPLATE is specified), the only difference is that templates
 | 
						|
 * are not added to the list of protocol instances and therefore not
 | 
						|
 * initialized during protos_commit()).
 | 
						|
 */
 | 
						|
void *
 | 
						|
proto_config_new(struct protocol *pr, int class)
 | 
						|
{
 | 
						|
  struct proto_config *cf = cfg_allocz(pr->config_size);
 | 
						|
 | 
						|
  if (class == SYM_PROTO)
 | 
						|
    add_tail(&new_config->protos, &cf->n);
 | 
						|
 | 
						|
  cf->global = new_config;
 | 
						|
  cf->protocol = pr;
 | 
						|
  cf->name = pr->name;
 | 
						|
  cf->class = class;
 | 
						|
  cf->debug = new_config->proto_default_debug;
 | 
						|
  cf->mrtdump = new_config->proto_default_mrtdump;
 | 
						|
  cf->loop_order = DOMAIN_ORDER(the_bird);
 | 
						|
 | 
						|
  init_list(&cf->channels);
 | 
						|
 | 
						|
  return cf;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * proto_copy_config - copy a protocol configuration
 | 
						|
 * @dest: destination protocol configuration
 | 
						|
 * @src: source protocol configuration
 | 
						|
 *
 | 
						|
 * Whenever a new instance of a routing protocol is created from the
 | 
						|
 * template, proto_copy_config() is called to copy a content of
 | 
						|
 * the source protocol configuration to the new protocol configuration.
 | 
						|
 * Name, class and a node in protos list of @dest are kept intact.
 | 
						|
 * copy_config() protocol hook is used to copy protocol-specific data.
 | 
						|
 */
 | 
						|
void
 | 
						|
proto_copy_config(struct proto_config *dest, struct proto_config *src)
 | 
						|
{
 | 
						|
  struct channel_config *cc;
 | 
						|
  node old_node;
 | 
						|
  int old_class;
 | 
						|
  const char *old_name;
 | 
						|
 | 
						|
  if (dest->protocol != src->protocol)
 | 
						|
    cf_error("Can't copy configuration from a different protocol type");
 | 
						|
 | 
						|
  if (dest->protocol->copy_config == NULL)
 | 
						|
    cf_error("Inheriting configuration for %s is not supported", src->protocol->name);
 | 
						|
 | 
						|
  DBG("Copying configuration from %s to %s\n", src->name, dest->name);
 | 
						|
 | 
						|
  /*
 | 
						|
   * Copy struct proto_config here. Keep original node, class and name.
 | 
						|
   * protocol-specific config copy is handled by protocol copy_config() hook
 | 
						|
   */
 | 
						|
 | 
						|
  old_node = dest->n;
 | 
						|
  old_class = dest->class;
 | 
						|
  old_name = dest->name;
 | 
						|
 | 
						|
  memcpy(dest, src, src->protocol->config_size);
 | 
						|
 | 
						|
  dest->n = old_node;
 | 
						|
  dest->class = old_class;
 | 
						|
  dest->name = old_name;
 | 
						|
  init_list(&dest->channels);
 | 
						|
 | 
						|
  WALK_LIST(cc, src->channels)
 | 
						|
    channel_copy_config(cc, dest);
 | 
						|
 | 
						|
  /* FIXME: allow for undefined copy_config */
 | 
						|
  dest->protocol->copy_config(dest, src);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
proto_clone_config(struct symbol *sym, struct proto_config *parent)
 | 
						|
{
 | 
						|
  struct proto_config *cf = proto_config_new(parent->protocol, SYM_PROTO);
 | 
						|
  proto_copy_config(cf, parent);
 | 
						|
  cf->name = sym->name;
 | 
						|
  cf->proto = NULL;
 | 
						|
  cf->parent = parent;
 | 
						|
 | 
						|
  sym->class = cf->class;
 | 
						|
  sym->proto = cf;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_undef_clone(struct symbol *sym, struct proto_config *cf)
 | 
						|
{
 | 
						|
  rem_node(&cf->n);
 | 
						|
 | 
						|
  sym->class = SYM_VOID;
 | 
						|
  sym->proto = NULL;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * protos_preconfig - pre-configuration processing
 | 
						|
 * @c: new configuration
 | 
						|
 *
 | 
						|
 * This function calls the preconfig() hooks of all routing
 | 
						|
 * protocols available to prepare them for reading of the new
 | 
						|
 * configuration.
 | 
						|
 */
 | 
						|
void
 | 
						|
protos_preconfig(struct config *c)
 | 
						|
{
 | 
						|
  struct protocol *p;
 | 
						|
 | 
						|
  init_list(&c->protos);
 | 
						|
  DBG("Protocol preconfig:");
 | 
						|
  WALK_LIST(p, protocol_list)
 | 
						|
  {
 | 
						|
    DBG(" %s", p->name);
 | 
						|
    p->name_counter = 0;
 | 
						|
    if (p->preconfig)
 | 
						|
      p->preconfig(p, c);
 | 
						|
  }
 | 
						|
  DBG("\n");
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config *nc, int type)
 | 
						|
{
 | 
						|
  /* If the protocol is DOWN, we just restart it */
 | 
						|
  if (p->proto_state == PS_DOWN)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  /* If there is a too big change in core attributes, ... */
 | 
						|
  if ((nc->protocol != oc->protocol) ||
 | 
						|
      (nc->net_type != oc->net_type) ||
 | 
						|
      (nc->disabled != p->disabled) ||
 | 
						|
      (nc->vrf != oc->vrf))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  p->sources.name = p->name = nc->name;
 | 
						|
  p->sources.debug = p->debug = nc->debug;
 | 
						|
  p->mrtdump = nc->mrtdump;
 | 
						|
  reconfigure_type = type;
 | 
						|
 | 
						|
  /* Execute protocol specific reconfigure hook */
 | 
						|
  if (!p->proto->reconfigure || !p->proto->reconfigure(p, nc))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  DBG("\t%s: same\n", oc->name);
 | 
						|
  PD(p, "Reconfigured");
 | 
						|
  p->cf = nc;
 | 
						|
 | 
						|
  return 1;
 | 
						|
}
 | 
						|
 | 
						|
static struct protos_commit_request {
 | 
						|
  struct config *new;
 | 
						|
  struct config *old;
 | 
						|
  enum protocol_startup phase;
 | 
						|
  int force_reconfig;
 | 
						|
  int type;
 | 
						|
} protos_commit_request;
 | 
						|
 | 
						|
static int proto_rethink_goal_pending = 0;
 | 
						|
 | 
						|
static void protos_do_commit(struct config *new, struct config *old, int force_reconfig, int type);
 | 
						|
 | 
						|
/**
 | 
						|
 * protos_commit - commit new protocol configuration
 | 
						|
 * @new: new configuration
 | 
						|
 * @old: old configuration or %NULL if it's boot time config
 | 
						|
 * @force_reconfig: force restart of all protocols (used for example
 | 
						|
 * when the router ID changes)
 | 
						|
 * @type: type of reconfiguration (RECONFIG_SOFT or RECONFIG_HARD)
 | 
						|
 *
 | 
						|
 * Scan differences between @old and @new configuration and adjust all
 | 
						|
 * protocol instances to conform to the new configuration.
 | 
						|
 *
 | 
						|
 * When a protocol exists in the new configuration, but it doesn't in the
 | 
						|
 * original one, it's immediately started. When a collision with the other
 | 
						|
 * running protocol would arise, the new protocol will be temporarily stopped
 | 
						|
 * by the locking mechanism.
 | 
						|
 *
 | 
						|
 * When a protocol exists in the old configuration, but it doesn't in the
 | 
						|
 * new one, it's shut down and deleted after the shutdown completes.
 | 
						|
 *
 | 
						|
 * When a protocol exists in both configurations, the core decides
 | 
						|
 * whether it's possible to reconfigure it dynamically - it checks all
 | 
						|
 * the core properties of the protocol (changes in filters are ignored
 | 
						|
 * if type is RECONFIG_SOFT) and if they match, it asks the
 | 
						|
 * reconfigure() hook of the protocol to see if the protocol is able
 | 
						|
 * to switch to the new configuration.  If it isn't possible, the
 | 
						|
 * protocol is shut down and a new instance is started with the new
 | 
						|
 * configuration after the shutdown is completed.
 | 
						|
 */
 | 
						|
void
 | 
						|
protos_commit(struct config *new, struct config *old, int force_reconfig, int type)
 | 
						|
{
 | 
						|
  protos_commit_request = (struct protos_commit_request) {
 | 
						|
    .new = new,
 | 
						|
    .old = old,
 | 
						|
    .phase = (new->shutdown && !new->gr_down) ? PROTOCOL_STARTUP_REGULAR : PROTOCOL_STARTUP_NECESSARY,
 | 
						|
    .force_reconfig = force_reconfig,
 | 
						|
    .type = type,
 | 
						|
  };
 | 
						|
 | 
						|
  protos_do_commit(new, old, force_reconfig, type);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
protos_do_commit(struct config *new, struct config *old, int force_reconfig, int type)
 | 
						|
{
 | 
						|
  enum protocol_startup phase = protos_commit_request.phase;
 | 
						|
  struct proto_config *oc, *nc;
 | 
						|
  struct symbol *sym;
 | 
						|
  struct proto *p;
 | 
						|
 | 
						|
  if ((phase < PROTOCOL_STARTUP_REGULAR) || (phase > PROTOCOL_STARTUP_NECESSARY))
 | 
						|
  {
 | 
						|
    protos_commit_request = (struct protos_commit_request) {};
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  DBG("protos_commit:\n");
 | 
						|
  if (old)
 | 
						|
  {
 | 
						|
    WALK_LIST(oc, old->protos)
 | 
						|
    {
 | 
						|
      if (oc->protocol->startup != phase)
 | 
						|
	continue;
 | 
						|
 | 
						|
      p = oc->proto;
 | 
						|
      sym = cf_find_symbol(new, oc->name);
 | 
						|
 | 
						|
      struct birdloop *proto_loop = PROTO_ENTER_FROM_MAIN(p);
 | 
						|
 | 
						|
      /* Handle dynamic protocols */
 | 
						|
      if (!sym && oc->parent && !new->shutdown)
 | 
						|
      {
 | 
						|
	struct symbol *parsym = cf_find_symbol(new, oc->parent->name);
 | 
						|
	if (parsym && parsym->class == SYM_PROTO)
 | 
						|
	{
 | 
						|
	  /* This is hack, we would like to share config, but we need to copy it now */
 | 
						|
	  new_config = new;
 | 
						|
	  cfg_mem = new->mem;
 | 
						|
	  new->current_scope = new->root_scope;
 | 
						|
	  sym = cf_get_symbol(new, oc->name);
 | 
						|
	  proto_clone_config(sym, parsym->proto);
 | 
						|
	  new_config = NULL;
 | 
						|
	  cfg_mem = NULL;
 | 
						|
	}
 | 
						|
      }
 | 
						|
 | 
						|
      if (sym && sym->class == SYM_PROTO && !new->shutdown)
 | 
						|
      {
 | 
						|
	/* Found match, let's check if we can smoothly switch to new configuration */
 | 
						|
	/* No need to check description */
 | 
						|
	nc = sym->proto;
 | 
						|
	nc->proto = p;
 | 
						|
 | 
						|
	/* We will try to reconfigure protocol p */
 | 
						|
	if (!force_reconfig && proto_reconfigure(p, oc, nc, type))
 | 
						|
	{
 | 
						|
	  PROTO_LEAVE_FROM_MAIN(proto_loop);
 | 
						|
	  continue;
 | 
						|
	}
 | 
						|
 | 
						|
	if (nc->parent)
 | 
						|
	{
 | 
						|
	  proto_undef_clone(sym, nc);
 | 
						|
	  goto remove;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Unsuccessful, we will restart it */
 | 
						|
	if (!p->disabled && !nc->disabled)
 | 
						|
	  log(L_INFO "Restarting protocol %s", p->name);
 | 
						|
	else if (p->disabled && !nc->disabled)
 | 
						|
	  log(L_INFO "Enabling protocol %s", p->name);
 | 
						|
	else if (!p->disabled && nc->disabled)
 | 
						|
	  log(L_INFO "Disabling protocol %s", p->name);
 | 
						|
 | 
						|
	p->down_code = nc->disabled ? PDC_CF_DISABLE : PDC_CF_RESTART;
 | 
						|
	p->cf_new = nc;
 | 
						|
      }
 | 
						|
      else if (!new->shutdown)
 | 
						|
      {
 | 
						|
      remove:
 | 
						|
	log(L_INFO "Removing protocol %s", p->name);
 | 
						|
	p->down_code = PDC_CF_REMOVE;
 | 
						|
	p->cf_new = NULL;
 | 
						|
      }
 | 
						|
      else if (new->gr_down)
 | 
						|
      {
 | 
						|
	p->down_code = PDC_CMD_GR_DOWN;
 | 
						|
	p->cf_new = NULL;
 | 
						|
      }
 | 
						|
      else /* global shutdown */
 | 
						|
      {
 | 
						|
	p->down_code = PDC_CMD_SHUTDOWN;
 | 
						|
	p->cf_new = NULL;
 | 
						|
      }
 | 
						|
 | 
						|
      p->reconfiguring = 1;
 | 
						|
      PROTO_LEAVE_FROM_MAIN(proto_loop);
 | 
						|
 | 
						|
      config_add_obstacle(old);
 | 
						|
      proto_rethink_goal(p);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  struct proto *after = NULL;
 | 
						|
 | 
						|
  WALK_LIST(nc, new->protos)
 | 
						|
    if ((nc->protocol->startup == phase) && !nc->proto)
 | 
						|
    {
 | 
						|
      /* Not a first-time configuration */
 | 
						|
      if (old)
 | 
						|
	log(L_INFO "Adding protocol %s", nc->name);
 | 
						|
 | 
						|
      p = proto_init(nc, after);
 | 
						|
      after = p;
 | 
						|
 | 
						|
      proto_rethink_goal(p);
 | 
						|
    }
 | 
						|
    else
 | 
						|
      after = nc->proto;
 | 
						|
 | 
						|
  DBG("Protocol start\n");
 | 
						|
 | 
						|
  /* Determine router ID for the first time - it has to be here and not in
 | 
						|
     global_commit() because it is postponed after start of device protocol */
 | 
						|
  if (!config->router_id)
 | 
						|
  {
 | 
						|
    config->router_id = if_choose_router_id(config->router_id_from, 0);
 | 
						|
    if (!config->router_id)
 | 
						|
      die("Cannot determine router ID, please configure it manually");
 | 
						|
  }
 | 
						|
 | 
						|
  /* Commit next round of protocols */
 | 
						|
  if (new->shutdown && !new->gr_down)
 | 
						|
    protos_commit_request.phase++;
 | 
						|
  else
 | 
						|
    protos_commit_request.phase--;
 | 
						|
 | 
						|
  /* If something is pending, the next round will be called asynchronously from proto_rethink_goal(). */
 | 
						|
  if (!proto_rethink_goal_pending)
 | 
						|
    protos_do_commit(new, old, force_reconfig, type);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_shutdown(struct proto *p)
 | 
						|
{
 | 
						|
  if (p->proto_state == PS_START || p->proto_state == PS_UP)
 | 
						|
  {
 | 
						|
    /* Going down */
 | 
						|
    DBG("Kicking %s down\n", p->name);
 | 
						|
    PD(p, "Shutting down");
 | 
						|
    proto_notify_state(p, (p->proto->shutdown ? p->proto->shutdown(p) : PS_DOWN));
 | 
						|
    if (p->reconfiguring)
 | 
						|
    {
 | 
						|
      proto_rethink_goal_pending++;
 | 
						|
      p->reconfiguring = 2;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_rethink_goal(struct proto *p)
 | 
						|
{
 | 
						|
  int goal_pending = (p->reconfiguring == 2);
 | 
						|
 | 
						|
  if (p->reconfiguring && !p->active)
 | 
						|
  {
 | 
						|
    struct proto_config *nc = p->cf_new;
 | 
						|
    struct proto *after = p->n.prev;
 | 
						|
 | 
						|
    DBG("%s has shut down for reconfiguration\n", p->name);
 | 
						|
    p->cf->proto = NULL;
 | 
						|
    config_del_obstacle(p->cf->global);
 | 
						|
    proto_remove_channels(p);
 | 
						|
    proto_rem_node(&global_proto_list, p);
 | 
						|
    rfree(p->event);
 | 
						|
    mb_free(p->message);
 | 
						|
    mb_free(p);
 | 
						|
    if (!nc)
 | 
						|
      goto done;
 | 
						|
 | 
						|
    p = proto_init(nc, after);
 | 
						|
  }
 | 
						|
 | 
						|
  /* Determine what state we want to reach */
 | 
						|
  if (p->disabled || p->reconfiguring)
 | 
						|
  {
 | 
						|
    PROTO_LOCKED_FROM_MAIN(p)
 | 
						|
      proto_shutdown(p);
 | 
						|
  }
 | 
						|
  else if (!p->active)
 | 
						|
    proto_start(p);
 | 
						|
 | 
						|
done:
 | 
						|
  if (goal_pending && !--proto_rethink_goal_pending)
 | 
						|
    protos_do_commit(
 | 
						|
	protos_commit_request.new,
 | 
						|
	protos_commit_request.old,
 | 
						|
	protos_commit_request.force_reconfig,
 | 
						|
	protos_commit_request.type
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
struct proto *
 | 
						|
proto_spawn(struct proto_config *cf, uint disabled)
 | 
						|
{
 | 
						|
  struct proto *p = proto_init(cf, global_proto_list.last);
 | 
						|
  p->disabled = disabled;
 | 
						|
  proto_rethink_goal(p);
 | 
						|
  return p;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * DOC: Graceful restart recovery
 | 
						|
 *
 | 
						|
 * Graceful restart of a router is a process when the routing plane (e.g. BIRD)
 | 
						|
 * restarts but both the forwarding plane (e.g kernel routing table) and routing
 | 
						|
 * neighbors keep proper routes, and therefore uninterrupted packet forwarding
 | 
						|
 * is maintained.
 | 
						|
 *
 | 
						|
 * BIRD implements graceful restart recovery by deferring export of routes to
 | 
						|
 * protocols until routing tables are refilled with the expected content. After
 | 
						|
 * start, protocols generate routes as usual, but routes are not propagated to
 | 
						|
 * them, until protocols report that they generated all routes. After that,
 | 
						|
 * graceful restart recovery is finished and the export (and the initial feed)
 | 
						|
 * to protocols is enabled.
 | 
						|
 *
 | 
						|
 * When graceful restart recovery need is detected during initialization, then
 | 
						|
 * enabled protocols are marked with @gr_recovery flag before start. Such
 | 
						|
 * protocols then decide how to proceed with graceful restart, participation is
 | 
						|
 * voluntary. Protocols could lock the recovery for each channel by function
 | 
						|
 * channel_graceful_restart_lock() (state stored in @gr_lock flag), which means
 | 
						|
 * that they want to postpone the end of the recovery until they converge and
 | 
						|
 * then unlock it. They also could set @gr_wait before advancing to %PS_UP,
 | 
						|
 * which means that the core should defer route export to that channel until
 | 
						|
 * the end of the recovery. This should be done by protocols that expect their
 | 
						|
 * neigbors to keep the proper routes (kernel table, BGP sessions with BGP
 | 
						|
 * graceful restart capability).
 | 
						|
 *
 | 
						|
 * The graceful restart recovery is finished when either all graceful restart
 | 
						|
 * locks are unlocked or when graceful restart wait timer fires.
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
static void graceful_restart_done(timer *t);
 | 
						|
 | 
						|
/**
 | 
						|
 * graceful_restart_recovery - request initial graceful restart recovery
 | 
						|
 *
 | 
						|
 * Called by the platform initialization code if the need for recovery
 | 
						|
 * after graceful restart is detected during boot. Have to be called
 | 
						|
 * before protos_commit().
 | 
						|
 */
 | 
						|
void
 | 
						|
graceful_restart_recovery(void)
 | 
						|
{
 | 
						|
  graceful_restart_state = GRS_INIT;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * graceful_restart_init - initialize graceful restart
 | 
						|
 *
 | 
						|
 * When graceful restart recovery was requested, the function starts an active
 | 
						|
 * phase of the recovery and initializes graceful restart wait timer. The
 | 
						|
 * function have to be called after protos_commit().
 | 
						|
 */
 | 
						|
void
 | 
						|
graceful_restart_init(void)
 | 
						|
{
 | 
						|
  if (!graceful_restart_state)
 | 
						|
    return;
 | 
						|
 | 
						|
  log(L_INFO "Graceful restart started");
 | 
						|
 | 
						|
  if (!graceful_restart_locks)
 | 
						|
  {
 | 
						|
    graceful_restart_done(NULL);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  graceful_restart_state = GRS_ACTIVE;
 | 
						|
  gr_wait_timer = tm_new_init(proto_pool, graceful_restart_done, NULL, 0, 0);
 | 
						|
  tm_start(gr_wait_timer, config->gr_wait S);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * graceful_restart_done - finalize graceful restart
 | 
						|
 * @t: unused
 | 
						|
 *
 | 
						|
 * When there are no locks on graceful restart, the functions finalizes the
 | 
						|
 * graceful restart recovery. Protocols postponing route export until the end of
 | 
						|
 * the recovery are awakened and the export to them is enabled. All other
 | 
						|
 * related state is cleared. The function is also called when the graceful
 | 
						|
 * restart wait timer fires (but there are still some locks).
 | 
						|
 */
 | 
						|
static void
 | 
						|
graceful_restart_done(timer *t UNUSED)
 | 
						|
{
 | 
						|
  log(L_INFO "Graceful restart done");
 | 
						|
  graceful_restart_state = GRS_DONE;
 | 
						|
 | 
						|
  WALK_TLIST(proto, p, &global_proto_list)
 | 
						|
  {
 | 
						|
    if (!p->gr_recovery)
 | 
						|
      continue;
 | 
						|
 | 
						|
    struct channel *c;
 | 
						|
    WALK_LIST(c, p->channels)
 | 
						|
    {
 | 
						|
      /* Resume postponed export of routes */
 | 
						|
      if ((c->channel_state == CS_UP) && c->gr_wait && p->rt_notify)
 | 
						|
	channel_start_export(c);
 | 
						|
 | 
						|
      /* Cleanup */
 | 
						|
      c->gr_wait = 0;
 | 
						|
      c->gr_lock = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    p->gr_recovery = 0;
 | 
						|
  }
 | 
						|
 | 
						|
  graceful_restart_locks = 0;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
graceful_restart_show_status(void)
 | 
						|
{
 | 
						|
  if (graceful_restart_state != GRS_ACTIVE)
 | 
						|
    return;
 | 
						|
 | 
						|
  cli_msg(-24, "Graceful restart recovery in progress");
 | 
						|
  cli_msg(-24, "  Waiting for %d channels to recover", graceful_restart_locks);
 | 
						|
  cli_msg(-24, "  Wait timer is %t/%u", tm_remains(gr_wait_timer), config->gr_wait);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * channel_graceful_restart_lock - lock graceful restart by channel
 | 
						|
 * @p: channel instance
 | 
						|
 *
 | 
						|
 * This function allows a protocol to postpone the end of graceful restart
 | 
						|
 * recovery until it converges. The lock is removed when the protocol calls
 | 
						|
 * channel_graceful_restart_unlock() or when the channel is closed.
 | 
						|
 *
 | 
						|
 * The function have to be called during the initial phase of graceful restart
 | 
						|
 * recovery and only for protocols that are part of graceful restart (i.e. their
 | 
						|
 * @gr_recovery is set), which means it should be called from protocol start
 | 
						|
 * hooks.
 | 
						|
 */
 | 
						|
void
 | 
						|
channel_graceful_restart_lock(struct channel *c)
 | 
						|
{
 | 
						|
  ASSERT(graceful_restart_state == GRS_INIT);
 | 
						|
  ASSERT(c->proto->gr_recovery);
 | 
						|
 | 
						|
  if (c->gr_lock)
 | 
						|
    return;
 | 
						|
 | 
						|
  c->gr_lock = 1;
 | 
						|
  graceful_restart_locks++;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * channel_graceful_restart_unlock - unlock graceful restart by channel
 | 
						|
 * @p: channel instance
 | 
						|
 *
 | 
						|
 * This function unlocks a lock from channel_graceful_restart_lock(). It is also
 | 
						|
 * automatically called when the lock holding protocol went down.
 | 
						|
 */
 | 
						|
void
 | 
						|
channel_graceful_restart_unlock(struct channel *c)
 | 
						|
{
 | 
						|
  if (!c->gr_lock)
 | 
						|
    return;
 | 
						|
 | 
						|
  c->gr_lock = 0;
 | 
						|
  graceful_restart_locks--;
 | 
						|
 | 
						|
  if ((graceful_restart_state == GRS_ACTIVE) && !graceful_restart_locks)
 | 
						|
    tm_start(gr_wait_timer, 0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * protos_dump_all - dump status of all protocols
 | 
						|
 *
 | 
						|
 * This function dumps status of all existing protocol instances to the
 | 
						|
 * debug output. It involves printing of general status information
 | 
						|
 * such as protocol states, its position on the protocol lists
 | 
						|
 * and also calling of a dump() hook of the protocol to print
 | 
						|
 * the internals.
 | 
						|
 */
 | 
						|
void
 | 
						|
protos_dump_all(void)
 | 
						|
{
 | 
						|
  debug("Protocols:\n");
 | 
						|
 | 
						|
  WALK_TLIST(proto, p, &global_proto_list) PROTO_LOCKED_FROM_MAIN(p)
 | 
						|
  {
 | 
						|
#define DPF(x)	(p->x ? " " #x : "")
 | 
						|
    debug("  protocol %s (%p) state %s with %d active channels flags: %s%s%s%s\n",
 | 
						|
	p->name, p, p_states[p->proto_state], p->active_channels,
 | 
						|
	DPF(disabled), DPF(active), DPF(do_stop), DPF(reconfiguring));
 | 
						|
#undef DPF
 | 
						|
 | 
						|
    struct channel *c;
 | 
						|
    WALK_LIST(c, p->channels)
 | 
						|
    {
 | 
						|
      debug("\tTABLE %s\n", c->table->name);
 | 
						|
      if (c->in_filter)
 | 
						|
	debug("\tInput filter: %s\n", filter_name(c->in_filter));
 | 
						|
      if (c->out_filter)
 | 
						|
	debug("\tOutput filter: %s\n", filter_name(c->out_filter));
 | 
						|
      debug("\tChannel state: %s/%s/%s\n", c_states[c->channel_state],
 | 
						|
	  c->in_req.hook ? rt_import_state_name(rt_import_get_state(c->in_req.hook)) : "-",
 | 
						|
	  c->out_req.hook ? rt_export_state_name(rt_export_get_state(c->out_req.hook)) : "-");
 | 
						|
    }
 | 
						|
 | 
						|
    debug("\tSOURCES\n");
 | 
						|
    rt_dump_sources(&p->sources);
 | 
						|
 | 
						|
    if (p->proto->dump && (p->proto_state != PS_DOWN))
 | 
						|
      p->proto->dump(p);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * proto_build - make a single protocol available
 | 
						|
 * @p: the protocol
 | 
						|
 *
 | 
						|
 * After the platform specific initialization code uses protos_build()
 | 
						|
 * to add all the standard protocols, it should call proto_build() for
 | 
						|
 * all platform specific protocols to inform the core that they exist.
 | 
						|
 */
 | 
						|
void
 | 
						|
proto_build(struct protocol *p)
 | 
						|
{
 | 
						|
  add_tail(&protocol_list, &p->n);
 | 
						|
}
 | 
						|
 | 
						|
/* FIXME: convert this call to some protocol hook */
 | 
						|
extern void bfd_init_all(void);
 | 
						|
 | 
						|
void protos_build_gen(void);
 | 
						|
 | 
						|
/**
 | 
						|
 * protos_build - build a protocol list
 | 
						|
 *
 | 
						|
 * This function is called during BIRD startup to insert
 | 
						|
 * all standard protocols to the global protocol list. Insertion
 | 
						|
 * of platform specific protocols (such as the kernel syncer)
 | 
						|
 * is in the domain of competence of the platform dependent
 | 
						|
 * startup code.
 | 
						|
 */
 | 
						|
void
 | 
						|
protos_build(void)
 | 
						|
{
 | 
						|
  proto_pool = rp_new(&root_pool, the_bird_domain.the_bird, "Protocols");
 | 
						|
 | 
						|
  protos_build_gen();
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* Temporary hack to propagate restart to BGP */
 | 
						|
int proto_restart;
 | 
						|
 | 
						|
static void
 | 
						|
proto_restart_event_hook(void *_p)
 | 
						|
{
 | 
						|
  struct proto *p = _p;
 | 
						|
  if (!p->down_sched)
 | 
						|
    return;
 | 
						|
 | 
						|
  proto_restart = (p->down_sched == PDS_RESTART);
 | 
						|
  p->disabled = 1;
 | 
						|
  proto_rethink_goal(p);
 | 
						|
 | 
						|
  p->restart_event = NULL;
 | 
						|
  p->restart_timer = NULL;
 | 
						|
 | 
						|
  if (proto_restart)
 | 
						|
    /* No need to call proto_rethink_goal() here again as the proto_cleanup() routine will
 | 
						|
     * call it after the protocol stops ... and both these routines are fixed to main_birdloop.
 | 
						|
     */
 | 
						|
    p->disabled = 0;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_send_restart_event(struct proto *p)
 | 
						|
{
 | 
						|
  if (!p->restart_event)
 | 
						|
    p->restart_event = ev_new_init(p->pool, proto_restart_event_hook, p);
 | 
						|
 | 
						|
  ev_send(&global_event_list, p->restart_event);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_send_restart_event_from_timer(struct timer *t)
 | 
						|
{
 | 
						|
  proto_send_restart_event((struct proto *) t->data);
 | 
						|
}
 | 
						|
 | 
						|
static inline void
 | 
						|
proto_schedule_down(struct proto *p, byte restart, byte code)
 | 
						|
{
 | 
						|
  /* Does not work for other states (even PS_START) */
 | 
						|
  ASSERT(p->proto_state == PS_UP);
 | 
						|
 | 
						|
  /* Scheduled restart may change to shutdown, but not otherwise */
 | 
						|
  if (p->down_sched == PDS_DISABLE)
 | 
						|
    return;
 | 
						|
 | 
						|
  p->down_sched = restart ? PDS_RESTART : PDS_DISABLE;
 | 
						|
  p->down_code = code;
 | 
						|
 | 
						|
  if (!restart)
 | 
						|
  {
 | 
						|
    if (p->restart_timer && tm_active(p->restart_timer))
 | 
						|
      tm_stop(p->restart_timer);
 | 
						|
 | 
						|
    proto_send_restart_event(p);
 | 
						|
  }
 | 
						|
  else
 | 
						|
  {
 | 
						|
    if (!p->restart_timer)
 | 
						|
      p->restart_timer = tm_new_init(p->pool, proto_send_restart_event_from_timer, p, 0, 0);
 | 
						|
 | 
						|
    tm_start_max_in(p->restart_timer, 250 MS, p->loop);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * proto_set_message - set administrative message to protocol
 | 
						|
 * @p: protocol
 | 
						|
 * @msg: message
 | 
						|
 * @len: message length (-1 for NULL-terminated string)
 | 
						|
 *
 | 
						|
 * The function sets administrative message (string) related to protocol state
 | 
						|
 * change. It is called by the nest code for manual enable/disable/restart
 | 
						|
 * commands all routes to the protocol, and by protocol-specific code when the
 | 
						|
 * protocol state change is initiated by the protocol. Using NULL message clears
 | 
						|
 * the last message. The message string may be either NULL-terminated or with an
 | 
						|
 * explicit length.
 | 
						|
 */
 | 
						|
void
 | 
						|
proto_set_message(struct proto *p, char *msg, int len)
 | 
						|
{
 | 
						|
  mb_free(p->message);
 | 
						|
  p->message = NULL;
 | 
						|
 | 
						|
  if (!msg || !len)
 | 
						|
    return;
 | 
						|
 | 
						|
  if (len < 0)
 | 
						|
    len = strlen(msg);
 | 
						|
 | 
						|
  if (!len)
 | 
						|
    return;
 | 
						|
 | 
						|
  p->message = mb_alloc(proto_pool, len + 1);
 | 
						|
  memcpy(p->message, msg, len);
 | 
						|
  p->message[len] = 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static const char * channel_limit_name[] = {
 | 
						|
  [PLA_WARN] = "warn",
 | 
						|
  [PLA_BLOCK] = "block",
 | 
						|
  [PLA_RESTART] = "restart",
 | 
						|
  [PLA_DISABLE] = "disable",
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
channel_log_limit(struct channel *c, struct limit *l, int dir)
 | 
						|
{
 | 
						|
  const char *dir_name[PLD_MAX] = { "receive", "import" , "export" };
 | 
						|
  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]]);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_activate_limit(struct channel *c, struct limit *l, int dir)
 | 
						|
{
 | 
						|
  if (c->limit_active & (1 << dir))
 | 
						|
    return;
 | 
						|
 | 
						|
  c->limit_active |= (1 << dir);
 | 
						|
  channel_log_limit(c, l, dir);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
channel_limit_warn(struct limit *l, void *data)
 | 
						|
{
 | 
						|
  struct channel_limit_data *cld = data;
 | 
						|
  struct channel *c = cld->c;
 | 
						|
  int dir = cld->dir;
 | 
						|
 | 
						|
  channel_log_limit(c, l, dir);
 | 
						|
 | 
						|
  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 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_data *cld = data;
 | 
						|
  struct channel *c = cld->c;
 | 
						|
  struct proto *p = c->proto;
 | 
						|
  int dir = cld->dir;
 | 
						|
 | 
						|
  channel_activate_limit(c, l, dir);
 | 
						|
 | 
						|
  if (p->proto_state == PS_UP)
 | 
						|
    proto_schedule_down(p, c->limit_actions[dir] == PLA_RESTART, chl_dir_down[dir]);
 | 
						|
 | 
						|
  return 1;
 | 
						|
}
 | 
						|
 | 
						|
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)
 | 
						|
{
 | 
						|
  limit_reset(l);
 | 
						|
  c->limit_active &= ~(1 << dir);
 | 
						|
}
 | 
						|
 | 
						|
static struct rte_owner_class default_rte_owner_class;
 | 
						|
 | 
						|
static inline void
 | 
						|
proto_do_start(struct proto *p)
 | 
						|
{
 | 
						|
  p->active = 1;
 | 
						|
 | 
						|
  rt_init_sources(&p->sources, p->name, proto_event_list(p));
 | 
						|
  if (!p->sources.class)
 | 
						|
    p->sources.class = &default_rte_owner_class;
 | 
						|
 | 
						|
  p->sources.debug = p->debug;
 | 
						|
 | 
						|
  if (!p->cf->late_if_feed)
 | 
						|
    iface_subscribe(&p->iface_sub);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_do_up(struct proto *p)
 | 
						|
{
 | 
						|
  if (!p->main_source)
 | 
						|
    p->main_source = rt_get_source(p, 0);
 | 
						|
    // Locked automaticaly
 | 
						|
 | 
						|
  proto_start_channels(p);
 | 
						|
 | 
						|
  if (p->cf->late_if_feed)
 | 
						|
    iface_subscribe(&p->iface_sub);
 | 
						|
}
 | 
						|
 | 
						|
static inline void
 | 
						|
proto_do_pause(struct proto *p)
 | 
						|
{
 | 
						|
  proto_pause_channels(p);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_do_stop(struct proto *p)
 | 
						|
{
 | 
						|
  p->down_sched = 0;
 | 
						|
  p->gr_recovery = 0;
 | 
						|
 | 
						|
  if (p->main_source)
 | 
						|
  {
 | 
						|
    rt_unlock_source(p->main_source);
 | 
						|
    p->main_source = NULL;
 | 
						|
  }
 | 
						|
 | 
						|
  rp_free(p->pool_up);
 | 
						|
  p->pool_up = NULL;
 | 
						|
 | 
						|
  proto_stop_channels(p);
 | 
						|
  rt_destroy_sources(&p->sources, p->event);
 | 
						|
 | 
						|
  p->do_stop = 1;
 | 
						|
  proto_send_event(p, p->event);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_do_down(struct proto *p)
 | 
						|
{
 | 
						|
  p->down_code = 0;
 | 
						|
 | 
						|
  /* Shutdown is finished in the protocol event */
 | 
						|
  if (proto_is_done(p))
 | 
						|
    proto_send_event(p, p->event);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * proto_notify_state - notify core about protocol state change
 | 
						|
 * @p: protocol the state of which has changed
 | 
						|
 * @ps: the new status
 | 
						|
 *
 | 
						|
 * Whenever a state of a protocol changes due to some event internal
 | 
						|
 * to the protocol (i.e., not inside a start() or shutdown() hook),
 | 
						|
 * it should immediately notify the core about the change by calling
 | 
						|
 * proto_notify_state() which will write the new state to the &proto
 | 
						|
 * structure and take all the actions necessary to adapt to the new
 | 
						|
 * state. State change to PS_DOWN immediately frees resources of protocol
 | 
						|
 * and might execute start callback of protocol; therefore,
 | 
						|
 * it should be used at tail positions of protocol callbacks.
 | 
						|
 */
 | 
						|
void
 | 
						|
proto_notify_state(struct proto *p, uint state)
 | 
						|
{
 | 
						|
  uint ps = p->proto_state;
 | 
						|
 | 
						|
  DBG("%s reporting state transition %s -> %s\n", p->name, p_states[ps], p_states[state]);
 | 
						|
  if (state == ps)
 | 
						|
    return;
 | 
						|
 | 
						|
  p->proto_state = state;
 | 
						|
  p->last_state_change = current_time();
 | 
						|
 | 
						|
  switch (state)
 | 
						|
  {
 | 
						|
  case PS_START:
 | 
						|
    ASSERT(ps == PS_DOWN || ps == PS_UP);
 | 
						|
 | 
						|
    if (ps == PS_DOWN)
 | 
						|
      proto_do_start(p);
 | 
						|
    else
 | 
						|
      proto_do_pause(p);
 | 
						|
    break;
 | 
						|
 | 
						|
  case PS_UP:
 | 
						|
    ASSERT(ps == PS_DOWN || ps == PS_START);
 | 
						|
 | 
						|
    if (ps == PS_DOWN)
 | 
						|
      proto_do_start(p);
 | 
						|
 | 
						|
    proto_do_up(p);
 | 
						|
    break;
 | 
						|
 | 
						|
  case PS_STOP:
 | 
						|
    ASSERT(ps == PS_START || ps == PS_UP);
 | 
						|
 | 
						|
    proto_do_stop(p);
 | 
						|
    break;
 | 
						|
 | 
						|
  case PS_DOWN:
 | 
						|
    if (ps != PS_STOP)
 | 
						|
      proto_do_stop(p);
 | 
						|
 | 
						|
    proto_do_down(p);
 | 
						|
    break;
 | 
						|
 | 
						|
  default:
 | 
						|
    bug("%s: Invalid state %d", p->name, ps);
 | 
						|
  }
 | 
						|
 | 
						|
  proto_log_state_change(p);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 *  CLI Commands
 | 
						|
 */
 | 
						|
 | 
						|
static char *
 | 
						|
proto_state_name(struct proto *p)
 | 
						|
{
 | 
						|
  switch (p->proto_state)
 | 
						|
  {
 | 
						|
  case PS_DOWN:		return p->active ? "flush" : "down";
 | 
						|
  case PS_START:	return "start";
 | 
						|
  case PS_UP:		return "up";
 | 
						|
  case PS_STOP:		return "stop";
 | 
						|
  default:		return "???";
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_show_stats(struct channel *c)
 | 
						|
{
 | 
						|
  struct channel_import_stats *ch_is = &c->import_stats;
 | 
						|
  struct channel_export_stats *ch_es = &c->export_stats;
 | 
						|
  struct rt_import_stats *rt_is = c->in_req.hook ? &c->in_req.hook->stats : NULL;
 | 
						|
  struct rt_export_stats *rt_es = c->out_req.hook ? &c->out_req.hook->stats : NULL;
 | 
						|
 | 
						|
#define SON(ie, item)	((ie) ? (ie)->item : 0)
 | 
						|
#define SCI(item) SON(ch_is, item)
 | 
						|
#define SCE(item) SON(ch_es, item)
 | 
						|
#define SRI(item) SON(rt_is, item)
 | 
						|
#define SRE(item) SON(rt_es, item)
 | 
						|
 | 
						|
  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)
 | 
						|
    cli_msg(-1006, "    Routes:         %u imported, %u filtered, %u exported, %u preferred",
 | 
						|
	    in_routes, (rx_routes - in_routes), out_routes, SRI(pref));
 | 
						|
  else
 | 
						|
    cli_msg(-1006, "    Routes:         %u imported, %u exported, %u preferred",
 | 
						|
	    in_routes, out_routes, SRI(pref));
 | 
						|
 | 
						|
  cli_msg(-1006, "    Route change stats:     received   rejected   filtered    ignored   RX limit   IN limit   accepted");
 | 
						|
  cli_msg(-1006, "      Import updates:     %10u %10u %10u %10u %10u %10u %10u",
 | 
						|
	  SCI(updates_received), SCI(updates_invalid),
 | 
						|
	  SCI(updates_filtered), SRI(updates_ignored),
 | 
						|
	  SCI(updates_limited_rx), SCI(updates_limited_in),
 | 
						|
	  SRI(updates_accepted));
 | 
						|
  cli_msg(-1006, "      Import withdraws:   %10u %10u        --- %10u        --- %10u",
 | 
						|
	  SCI(withdraws_received), SCI(withdraws_invalid),
 | 
						|
	  SRI(withdraws_ignored), SRI(withdraws_accepted));
 | 
						|
  cli_msg(-1006, "      Export updates:     %10u %10u %10u        --- %10u %10u",
 | 
						|
	  SRE(updates_received), SCE(updates_rejected),
 | 
						|
	  SCE(updates_filtered), SCE(updates_limited), SCE(updates_accepted));
 | 
						|
  cli_msg(-1006, "      Export withdraws:   %10u        ---        ---        ---         ---%10u",
 | 
						|
	  SRE(withdraws_received), SCE(withdraws_accepted));
 | 
						|
 | 
						|
#undef SRI
 | 
						|
#undef SRE
 | 
						|
#undef SCI
 | 
						|
#undef SCE
 | 
						|
#undef SON
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
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->max, active ? " [HIT]" : "");
 | 
						|
  cli_msg(-1006, "      Action:       %s", channel_limit_name[action]);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
channel_show_info(struct channel *c)
 | 
						|
{
 | 
						|
  cli_msg(-1006, "  Channel %s", c->name);
 | 
						|
  cli_msg(-1006, "    State:          %s", c_states[c->channel_state]);
 | 
						|
  cli_msg(-1006, "    Import state:   %s", rt_import_state_name(rt_import_get_state(c->in_req.hook)));
 | 
						|
  cli_msg(-1006, "    Export state:   %s", rt_export_state_name(rt_export_get_state(c->out_req.hook)));
 | 
						|
  cli_msg(-1006, "    Table:          %s", c->table->name);
 | 
						|
  cli_msg(-1006, "    Preference:     %d", c->preference);
 | 
						|
  cli_msg(-1006, "    Input filter:   %s", filter_name(c->in_filter));
 | 
						|
  cli_msg(-1006, "    Output filter:  %s", filter_name(c->out_filter));
 | 
						|
 | 
						|
  if (graceful_restart_state == GRS_ACTIVE)
 | 
						|
    cli_msg(-1006, "    GR recovery:   %s%s",
 | 
						|
	    c->gr_lock ? " pending" : "",
 | 
						|
	    c->gr_wait ? " waiting" : "");
 | 
						|
 | 
						|
  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);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
channel_cmd_debug(struct channel *c, uint mask)
 | 
						|
{
 | 
						|
  if (cli_access_restricted())
 | 
						|
    return;
 | 
						|
 | 
						|
  c->debug = mask;
 | 
						|
  cli_msg(0, "");
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
proto_cmd_show(struct proto *p, uintptr_t verbose, int cnt)
 | 
						|
{
 | 
						|
  byte buf[256], tbuf[TM_DATETIME_BUFFER_SIZE];
 | 
						|
 | 
						|
  /* First protocol - show header */
 | 
						|
  if (!cnt)
 | 
						|
    cli_msg(-2002, "%-10s %-10s %-10s %-6s %-12s  %s",
 | 
						|
	    "Name", "Proto", "Table", "State", "Since", "Info");
 | 
						|
 | 
						|
  buf[0] = 0;
 | 
						|
  if (p->proto->get_status)
 | 
						|
    p->proto->get_status(p, buf);
 | 
						|
  tm_format_time(tbuf, &config->tf_proto, p->last_state_change);
 | 
						|
  cli_msg(-1002, "%-10s %-10s %-10s %-6s %-12s  %s",
 | 
						|
	  p->name,
 | 
						|
	  p->proto->name,
 | 
						|
	  p->main_channel ? p->main_channel->table->name : "---",
 | 
						|
	  proto_state_name(p),
 | 
						|
	  tbuf,
 | 
						|
	  buf);
 | 
						|
 | 
						|
  if (verbose)
 | 
						|
  {
 | 
						|
    if (p->cf->dsc)
 | 
						|
      cli_msg(-1006, "  Description:    %s", p->cf->dsc);
 | 
						|
    if (p->message)
 | 
						|
      cli_msg(-1006, "  Message:        %s", p->message);
 | 
						|
    if (p->cf->router_id)
 | 
						|
      cli_msg(-1006, "  Router ID:      %R", p->cf->router_id);
 | 
						|
    if (p->vrf)
 | 
						|
      cli_msg(-1006, "  VRF:            %s", p->vrf->name);
 | 
						|
 | 
						|
    if (p->proto->show_proto_info)
 | 
						|
      p->proto->show_proto_info(p);
 | 
						|
    else
 | 
						|
    {
 | 
						|
      struct channel *c;
 | 
						|
      WALK_LIST(c, p->channels)
 | 
						|
	channel_show_info(c);
 | 
						|
    }
 | 
						|
 | 
						|
    cli_msg(-1006, "");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
proto_cmd_disable(struct proto *p, uintptr_t arg, int cnt UNUSED)
 | 
						|
{
 | 
						|
  if (p->disabled)
 | 
						|
  {
 | 
						|
    cli_msg(-8, "%s: already disabled", p->name);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  log(L_INFO "Disabling protocol %s", p->name);
 | 
						|
  p->disabled = 1;
 | 
						|
  p->down_code = PDC_CMD_DISABLE;
 | 
						|
  proto_set_message(p, (char *) arg, -1);
 | 
						|
  proto_shutdown(p);
 | 
						|
  cli_msg(-9, "%s: disabled", p->name);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
proto_cmd_enable(struct proto *p, uintptr_t arg, int cnt UNUSED)
 | 
						|
{
 | 
						|
  if (!p->disabled)
 | 
						|
  {
 | 
						|
    cli_msg(-10, "%s: already enabled", p->name);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  log(L_INFO "Enabling protocol %s", p->name);
 | 
						|
  p->disabled = 0;
 | 
						|
  proto_set_message(p, (char *) arg, -1);
 | 
						|
  proto_rethink_goal(p);
 | 
						|
  cli_msg(-11, "%s: enabled", p->name);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
proto_cmd_restart(struct proto *p, uintptr_t arg, int cnt UNUSED)
 | 
						|
{
 | 
						|
  if (p->disabled)
 | 
						|
  {
 | 
						|
    cli_msg(-8, "%s: already disabled", p->name);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  log(L_INFO "Restarting protocol %s", p->name);
 | 
						|
  p->disabled = 1;
 | 
						|
  p->down_code = PDC_CMD_RESTART;
 | 
						|
  proto_set_message(p, (char *) arg, -1);
 | 
						|
  proto_shutdown(p);
 | 
						|
  p->disabled = 0;
 | 
						|
  /* After the protocol shuts down, proto_rethink_goal() is run from proto_event. */
 | 
						|
  cli_msg(-12, "%s: restarted", p->name);
 | 
						|
}
 | 
						|
 | 
						|
struct channel_cmd_reload_feeding_request {
 | 
						|
  struct channel_feeding_request cfr;
 | 
						|
  struct proto_reload_request *prr;
 | 
						|
};
 | 
						|
 | 
						|
struct channel_cmd_reload_import_request {
 | 
						|
  struct channel_import_request cir;
 | 
						|
  struct proto_reload_request *prr;
 | 
						|
};
 | 
						|
 | 
						|
static void
 | 
						|
channel_reload_out_done(struct channel_feeding_request *cfr)
 | 
						|
{
 | 
						|
  struct channel_cmd_reload_feeding_request *ccrfr = SKIP_BACK(struct channel_cmd_reload_feeding_request, cfr, cfr);
 | 
						|
  if (atomic_fetch_sub_explicit(&ccrfr->prr->counter, 1, memory_order_acq_rel) == 1)
 | 
						|
    ev_send_loop(&main_birdloop, &ccrfr->prr->ev);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
channel_reload_in_done(struct channel_import_request *cir)
 | 
						|
{
 | 
						|
  struct channel_cmd_reload_import_request *ccrir = SKIP_BACK(struct channel_cmd_reload_import_request, cir, cir);
 | 
						|
  if (atomic_fetch_sub_explicit(&ccrir->prr->counter, 1, memory_order_acq_rel) == 1)
 | 
						|
    ev_send_loop(&main_birdloop, &ccrir->prr->ev);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
proto_cmd_reload(struct proto *p, uintptr_t _prr, int cnt UNUSED)
 | 
						|
{
 | 
						|
  struct proto_reload_request *prr = (void *) _prr;
 | 
						|
  struct channel *c;
 | 
						|
  if (p->disabled)
 | 
						|
  {
 | 
						|
    cli_msg(-8, "%s: already disabled", p->name);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  /* If the protocol in not UP, it has no routes */
 | 
						|
  if (p->proto_state != PS_UP)
 | 
						|
    return;
 | 
						|
 | 
						|
  /* All channels must support reload */
 | 
						|
  if (prr->dir & CMD_RELOAD_IN)
 | 
						|
    WALK_LIST(c, p->channels)
 | 
						|
      if ((c->channel_state == CS_UP) && !channel_reloadable(c))
 | 
						|
      {
 | 
						|
	cli_msg(-8006, "%s: reload failed", p->name);
 | 
						|
	return;
 | 
						|
      }
 | 
						|
 | 
						|
  log(L_INFO "Reloading protocol %s", p->name);
 | 
						|
 | 
						|
  /* re-importing routes */
 | 
						|
  if (prr->dir & CMD_RELOAD_IN)
 | 
						|
    WALK_LIST(c, p->channels)
 | 
						|
      if (c->channel_state == CS_UP)
 | 
						|
      {
 | 
						|
        if (prr->trie)
 | 
						|
	{
 | 
						|
	  /* Increase the refeed counter */
 | 
						|
	  atomic_fetch_add_explicit(&prr->counter, 1, memory_order_relaxed);
 | 
						|
	  ASSERT_DIE(this_cli->parser_pool != prr->trie->lp);
 | 
						|
 | 
						|
	  struct channel_cmd_reload_import_request *req = lp_alloc(prr->trie->lp, sizeof *req);
 | 
						|
	  *req = (struct channel_cmd_reload_import_request) {
 | 
						|
	    .cir = {
 | 
						|
	      .done = channel_reload_in_done,
 | 
						|
	      .trie = prr->trie,
 | 
						|
	    },
 | 
						|
	    .prr = prr,
 | 
						|
	  };
 | 
						|
	  channel_request_partial_reload(c, &req->cir);
 | 
						|
        }
 | 
						|
        else
 | 
						|
	  channel_request_reload(c);
 | 
						|
      }
 | 
						|
 | 
						|
  /* re-exporting routes */
 | 
						|
  if (prr->dir & CMD_RELOAD_OUT)
 | 
						|
    WALK_LIST(c, p->channels)
 | 
						|
      if ((c->channel_state == CS_UP) && (c->out_req.hook))
 | 
						|
        if (prr->trie)
 | 
						|
	{
 | 
						|
	  /* Increase the refeed counter */
 | 
						|
	  atomic_fetch_add_explicit(&prr->counter, 1, memory_order_relaxed);
 | 
						|
	  ASSERT_DIE(this_cli->parser_pool != prr->trie->lp);
 | 
						|
 | 
						|
	  /* Request actually the feeding */
 | 
						|
 | 
						|
	  struct channel_cmd_reload_feeding_request *req = lp_alloc(prr->trie->lp, sizeof *req);
 | 
						|
	  *req = (struct channel_cmd_reload_feeding_request) {
 | 
						|
	    .cfr = {
 | 
						|
	      .type = CFRT_AUXILIARY,
 | 
						|
	      .done = channel_reload_out_done,
 | 
						|
	      .trie = prr->trie,
 | 
						|
	    },
 | 
						|
	    .prr = prr,
 | 
						|
	  };
 | 
						|
 | 
						|
	  channel_request_feeding(c, &req->cfr);
 | 
						|
	}
 | 
						|
	else
 | 
						|
	  channel_request_feeding_dynamic(c, CFRT_AUXILIARY);
 | 
						|
 | 
						|
  cli_msg(-15, "%s: reloading", p->name);
 | 
						|
}
 | 
						|
 | 
						|
extern void pipe_update_debug(struct proto *P);
 | 
						|
 | 
						|
void
 | 
						|
proto_cmd_debug(struct proto *p, uintptr_t mask, int cnt UNUSED)
 | 
						|
{
 | 
						|
  p->debug = mask;
 | 
						|
 | 
						|
#ifdef CONFIG_PIPE
 | 
						|
  if (p->proto == &proto_pipe)
 | 
						|
    pipe_update_debug(p);
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
proto_cmd_mrtdump(struct proto *p, uintptr_t mask, int cnt UNUSED)
 | 
						|
{
 | 
						|
  p->mrtdump = mask;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_apply_cmd_symbol(const struct symbol *s, void (* cmd)(struct proto *, uintptr_t, int), uintptr_t arg)
 | 
						|
{
 | 
						|
  if (s->class != SYM_PROTO)
 | 
						|
  {
 | 
						|
    cli_msg(9002, "%s is not a protocol", s->name);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (s->proto->proto)
 | 
						|
  {
 | 
						|
    struct proto *p = s->proto->proto;
 | 
						|
    PROTO_LOCKED_FROM_MAIN(p)
 | 
						|
      cmd(p, arg, 0);
 | 
						|
    cli_msg(0, "");
 | 
						|
  }
 | 
						|
  else
 | 
						|
    cli_msg(9002, "%s does not exist", s->name);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proto_apply_cmd_patt(const char *patt, void (* cmd)(struct proto *, uintptr_t, int), uintptr_t arg)
 | 
						|
{
 | 
						|
  int cnt = 0;
 | 
						|
 | 
						|
  WALK_TLIST(proto, p, &global_proto_list)
 | 
						|
    if (!patt || patmatch(patt, p->name))
 | 
						|
      PROTO_LOCKED_FROM_MAIN(p)
 | 
						|
	cmd(p, arg, cnt++);
 | 
						|
 | 
						|
  if (!cnt)
 | 
						|
    cli_msg(8003, "No protocols match");
 | 
						|
  else
 | 
						|
    cli_msg(0, "");
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
proto_apply_cmd(struct proto_spec ps, void (* cmd)(struct proto *, uintptr_t, int),
 | 
						|
		int restricted, uintptr_t arg)
 | 
						|
{
 | 
						|
  if (restricted && cli_access_restricted())
 | 
						|
    return;
 | 
						|
 | 
						|
  if (ps.patt)
 | 
						|
    proto_apply_cmd_patt(ps.ptr, cmd, arg);
 | 
						|
  else
 | 
						|
    proto_apply_cmd_symbol(ps.ptr, cmd, arg);
 | 
						|
}
 | 
						|
 | 
						|
struct proto *
 | 
						|
proto_get_named(struct symbol *sym, struct protocol *pr)
 | 
						|
{
 | 
						|
  struct proto *p;
 | 
						|
 | 
						|
  if (sym)
 | 
						|
  {
 | 
						|
    if (sym->class != SYM_PROTO)
 | 
						|
      cf_error("%s: Not a protocol", sym->name);
 | 
						|
 | 
						|
    p = sym->proto->proto;
 | 
						|
    if (!p || p->proto != pr)
 | 
						|
      cf_error("%s: Not a %s protocol", sym->name, pr->name);
 | 
						|
  }
 | 
						|
  else
 | 
						|
  {
 | 
						|
    p = NULL;
 | 
						|
    WALK_TLIST(proto, q, &global_proto_list)
 | 
						|
      if ((q->proto == pr) && (q->proto_state != PS_DOWN))
 | 
						|
      {
 | 
						|
	if (p)
 | 
						|
	  cf_error("There are multiple %s protocols running", pr->name);
 | 
						|
	p = q;
 | 
						|
      }
 | 
						|
    if (!p)
 | 
						|
      cf_error("There is no %s protocol running", pr->name);
 | 
						|
  }
 | 
						|
 | 
						|
  return p;
 | 
						|
}
 | 
						|
 | 
						|
struct proto *
 | 
						|
proto_iterate_named(struct symbol *sym, struct protocol *proto, struct proto *old)
 | 
						|
{
 | 
						|
  if (sym)
 | 
						|
  {
 | 
						|
    /* Just the first pass */
 | 
						|
    if (old)
 | 
						|
    {
 | 
						|
      cli_msg(0, "");
 | 
						|
      return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    if (sym->class != SYM_PROTO)
 | 
						|
      cf_error("%s: Not a protocol", sym->name);
 | 
						|
 | 
						|
    struct proto *p = sym->proto->proto;
 | 
						|
    if (!p || (p->proto != proto))
 | 
						|
      cf_error("%s: Not a %s protocol", sym->name, proto->name);
 | 
						|
 | 
						|
    return p;
 | 
						|
  }
 | 
						|
  else
 | 
						|
  {
 | 
						|
    for (struct proto *p = old ? old->n.next : global_proto_list.first;
 | 
						|
	p;
 | 
						|
	p = p->n.next)
 | 
						|
    {
 | 
						|
      if ((p->proto == proto) && (p->proto_state != PS_DOWN))
 | 
						|
      {
 | 
						|
	cli_separator(this_cli);
 | 
						|
	return p;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    /* Not found anything during first pass */
 | 
						|
    if (!old)
 | 
						|
      cf_error("There is no %s protocol running", proto->name);
 | 
						|
 | 
						|
    /* No more items */
 | 
						|
    cli_msg(0, "");
 | 
						|
    return NULL;
 | 
						|
  }
 | 
						|
}
 |