]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Automatic ROA reloads on channel import
authorKaterina Kubecova <katerina.kubecova@nic.cz>
Thu, 2 Nov 2023 13:33:00 +0000 (14:33 +0100)
committerMaria Matejka <mq@ucw.cz>
Thu, 2 Nov 2023 13:37:27 +0000 (14:37 +0100)
This includes updating OSPF, Pipe and RIP to enable partial route reload
directly from the protocols' internal tables.

17 files changed:
conf/confbase.Y
doc/bird.sgml
nest/config.Y
nest/proto.c
nest/protocol.h
nest/rt.h
proto/bgp/attrs.c
proto/bgp/bgp.c
proto/ospf/ospf.c
proto/ospf/ospf.h
proto/ospf/rt.c
proto/pipe/pipe.c
proto/pipe/pipe.h
proto/rip/rip.c
proto/rip/rip.h
proto/static/static.c
sysdep/unix/krt.c

index 8e5da9e3f792e86f5ecea4dbf5ef13808fe5bfdc..c764f9e4f2cb9fc7e2048698918e0e4abc28d8e0 100644 (file)
@@ -81,6 +81,7 @@ CF_DECLS
   const struct filter *f;
   struct f_tree *e;
   struct f_trie *trie;
+  const struct f_trie *const_trie;
   struct f_val v;
   struct password_item *p;
   struct rt_show_data *ra;
index 0da616c700c0265c8c70a53c811a4bd3e934bf49..e90e2409084f7b683b39ef14ec53eeb10e5d5900 100644 (file)
@@ -979,7 +979,8 @@ inherited from templates can be updated by new definitions.
        <cf/roa_check()/ operator). In contrast to to other filter operators,
        this status for the same route may change as the content of ROA tables
        changes. When this option is active, BIRD activates automatic reload of
-       affected channels whenever ROA tables are updated (after a short settle
+       the appropriate subset of prefixes imported or exported by the channels
+       whenever ROA tables are updated (after a short settle
        time). When disabled, route reloads have to be requested manually. The
        option is ignored if <cf/roa_check()/ is not used in channel filters.
        Note that for BGP channels, automatic reload requires
@@ -1263,7 +1264,7 @@ This argument can be omitted if there exists only a single instance.
        Enable, disable or restart a given protocol instance, instances matching
        the <cf><m/pattern/</cf> or <cf/all/ instances.
 
-       <tag><label id="cli-reload">reload [in|out] <m/name/|"<m/pattern/"|all</tag>
+       <tag><label id="cli-reload">reload [in|out] (<m/name/|"<m/pattern/"|all) [partial prefix] </tag>
        Reload a given protocol instance, that means re-import routes from the
        protocol instance and re-export preferred routes to the instance. If
        <cf/in/ or <cf/out/ options are used, the command is restricted to one
@@ -1274,6 +1275,9 @@ This argument can be omitted if there exists only a single instance.
        propagates the old set of routes. For example when <cf/configure soft/
        command was used to change filters.
 
+       If <cf/partial prefix/ option is used, only corresponding routes are reloaded.
+       Protocol BGP does partial reload only if it has import table enabled, otherwise partial reload for BGP is refused.
+
        Re-export always succeeds, but re-import is protocol-dependent and might
        fail (for example, if BGP neighbor does not support route-refresh
        extension). In that case, re-export is also skipped. Note that for the
index 2e70461ae309b8480991fc96732a59634ff023e5..f871ac2a5c51864f7d582cd62045dcfd734b210d 100644 (file)
@@ -107,6 +107,44 @@ proto_postconfig(void)
   this_proto = NULL;
 }
 
+static void
+channel_reload_out_done_main(void *_prr)
+{
+  struct proto_reload_request *prr = _prr;
+  ASSERT_THE_BIRD_LOCKED;
+
+  rfree(prr->trie->lp);
+}
+
+static inline void
+proto_call_cmd_reload(struct proto_spec ps, int dir, const struct f_trie *trie)
+{
+  struct proto_reload_request *prr = cfg_alloc(sizeof *prr);
+  *prr = (struct proto_reload_request) {
+    .trie = trie,
+    .dir = dir,
+    .counter = 1,
+  };
+  if (trie)
+  {
+    /* CLI linpool is moved to trie, because trie is need for longer time
+     * than the linpool would exist in CLI. The linpool is freed in channel_reload_out_done_main.
+     */
+    ASSERT_DIE(this_cli->parser_pool == prr->trie->lp);
+    rmove(this_cli->parser_pool, &root_pool);
+    this_cli->parser_pool = lp_new(this_cli->pool);
+    prr->ev = (event) {
+      .data = prr,
+      .hook = channel_reload_out_done_main,
+    };
+  }
+
+  proto_apply_cmd(ps, proto_cmd_reload, 1, (uintptr_t) prr);
+  /* This function used reference to this trie, so it is freed here as well as in protocols*/
+  if (trie)
+    if (atomic_fetch_sub_explicit(&prr->counter, 1, memory_order_acq_rel) == 1)
+      ev_send_loop(&main_birdloop, &prr->ev);
+}
 
 #define DIRECT_CFG ((struct rt_dev_config *) this_proto)
 
@@ -120,7 +158,7 @@ CF_KEYWORDS(PASSWORD, KEY, FROM, PASSIVE, TO, ID, EVENTS, PACKETS, PROTOCOLS, CH
 CF_KEYWORDS(ALGORITHM, KEYED, HMAC, MD5, SHA1, SHA256, SHA384, SHA512, BLAKE2S128, BLAKE2S256, BLAKE2B256, BLAKE2B512)
 CF_KEYWORDS(PRIMARY, STATS, COUNT, FOR, IN, COMMANDS, PREEXPORT, NOEXPORT, EXPORTED, GENERATE)
 CF_KEYWORDS(BGP, PASSWORDS, DESCRIPTION)
-CF_KEYWORDS(RELOAD, IN, OUT, MRTDUMP, MESSAGES, RESTRICT, MEMORY, CLASS, DSCP)
+CF_KEYWORDS(RELOAD, IN, OUT, MRTDUMP, MESSAGES, RESTRICT, MEMORY, CLASS, DSCP, PARTIAL)
 CF_KEYWORDS(TIMEFORMAT, ISO, SHORT, LONG, ROUTE, PROTOCOL, BASE, LOG, S, MS, US)
 CF_KEYWORDS(GRACEFUL, RESTART, WAIT, MAX, AS)
 CF_KEYWORDS(MIN, IDLE, RX, TX, INTERVAL, MULTIPLIER, PASSIVE)
@@ -151,6 +189,7 @@ CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6)
 %type <net_ptr> r_args_for
 %type <t> channel_sym
 %type <c> channel_arg
+%type <const_trie> partial_opt
 
 CF_GRAMMAR
 
@@ -892,18 +931,28 @@ CF_CLI(DUMP FILTER ALL,,, [[Dump all filters in linearized form]])
 CF_CLI(EVAL, term, <expr>, [[Evaluate an expression]])
 { cmd_eval(f_linearize($2, 1)); } ;
 
+partial_opt:
+  PARTIAL term {
+    struct f_val val;
+    if (f_eval(f_linearize($2, 1), &val) > F_RETURN) cf_error("Runtime error");
+    if (val.type != T_PREFIX_SET) cf_error("Partial spec must be prefix set");
+    $$ = val.val.ti;
+  }
+  | /* empty */ { $$ = NULL; }
+  ;
+
 CF_CLI(DISABLE, proto_patt opttext, (<protocol> | \"<pattern>\" | all) [message], [[Disable protocol]])
 { proto_apply_cmd($2, proto_cmd_disable, 1, (uintptr_t) $3); } ;
 CF_CLI(ENABLE, proto_patt opttext, (<protocol> | \"<pattern>\" | all) [message], [[Enable protocol]])
 { proto_apply_cmd($2, proto_cmd_enable, 1, (uintptr_t) $3); } ;
 CF_CLI(RESTART, proto_patt opttext, (<protocol> | \"<pattern>\" | all) [message], [[Restart protocol]])
 { proto_apply_cmd($2, proto_cmd_restart, 1, (uintptr_t) $3); } ;
-CF_CLI(RELOAD, proto_patt, <protocol> | \"<pattern>\" | all, [[Reload protocol]])
-{ proto_apply_cmd($2, proto_cmd_reload, 1, CMD_RELOAD); } ;
-CF_CLI(RELOAD IN, proto_patt, <protocol> | \"<pattern>\" | all, [[Reload protocol (just imported routes)]])
-{ proto_apply_cmd($3, proto_cmd_reload, 1, CMD_RELOAD_IN); } ;
-CF_CLI(RELOAD OUT, proto_patt, <protocol> | \"<pattern>\" | all, [[Reload protocol (just exported routes)]])
-{ proto_apply_cmd($3, proto_cmd_reload, 1, CMD_RELOAD_OUT); } ;
+CF_CLI(RELOAD, proto_patt partial_opt, (<protocol> | \"<pattern>\" | all) [partial <prefix set>], [[Reload protocol]])
+{ proto_call_cmd_reload($2, CMD_RELOAD, $3); } ;
+CF_CLI(RELOAD IN, proto_patt partial_opt, <protocol> | \"<pattern>\" | all, [[Reload protocol (just imported routes)]])
+{ proto_call_cmd_reload($3, CMD_RELOAD_IN, $4); } ;
+CF_CLI(RELOAD OUT, proto_patt partial_opt, <protocol> | \"<pattern>\" | all, [[Reload protocol (just exported routes)]])
+{ proto_call_cmd_reload($3, CMD_RELOAD_OUT, $4); } ;
 
 CF_CLI_HELP(DEBUG, ..., [[Control protocol debugging via BIRD logs]])
 CF_CLI(DEBUG, debug_args, (<protocol> | <channel> | \"<pattern>\" | all) (all | off | { states|routes|filters|interfaces|events|packets [, ...] }), [[Control protocol debugging via BIRD logs]])
index 767395565dd0e3001697dc709f77d00511f2b427..e569ab76960c1a521a083b6b8d38fa9f28327ad4 100644 (file)
@@ -52,11 +52,14 @@ static void channel_init_limit(struct channel *c, struct limit *l, int dir, stru
 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); }
@@ -339,15 +342,109 @@ proto_remove_channels(struct proto *p)
     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;
   struct rt_export_request req;
-  struct f_trie* trie;
-  struct channel_feeding_request cfr[2];
+  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)
 {
@@ -355,7 +452,15 @@ channel_roa_in_changed(struct settle *se)
   struct channel *c = s->c;
 
   CD(c, "Reload triggered by RPKI change");
-  channel_request_reload(c);
+  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
@@ -372,15 +477,19 @@ channel_roa_out_changed(struct settle *se)
 
   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,
   };
-  channel_request_feeding(c, cfr);
 
+  /* 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
@@ -623,7 +732,6 @@ channel_start_export(struct channel *c)
   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;
-  c->refeed_req.prefilter.hook = channel_refeed_prefilter;
 
   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);
@@ -709,6 +817,8 @@ channel_refeed_stopped(struct rt_export_request *req)
 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)
     {
@@ -716,12 +826,25 @@ channel_init_feeding(struct channel *c)
       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;
   c->refeed_trie = f_new_trie(lp_new(c->proto->pool), 0);
 
+  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);
 }
 
@@ -733,13 +856,35 @@ channel_refeed_prefilter(const struct rt_prefilter *p, const net_addr *n)
        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)
@@ -791,9 +936,15 @@ channel_feed_end(struct channel *c)
 
 /* Called by protocol for reload from in_table */
 void
-channel_schedule_reload(struct channel *c)
+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)
   {
@@ -802,7 +953,29 @@ channel_schedule_reload(struct channel *c)
     return;
   }
 
-  rt_refresh_begin(&c->in_req);
+  /* 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);
 }
 
@@ -1013,7 +1186,11 @@ channel_set_state(struct channel *c, uint state)
 void
 channel_request_feeding(struct channel *c, struct channel_feeding_request *cfr)
 {
-  ASSERT(c->out_req.hook);
+  ASSERT_DIE(c->out_req.hook);
+
+  CD(c, "Feeding requested (%s)",
+      cfr->type == CFRT_DIRECT ? "direct" :
+      (cfr->trie ? "partial" : "auxiliary"));
 
   /* Enqueue the request */
   cfr->next = c->refeed_pending;
@@ -1052,6 +1229,12 @@ channel_stop_export(struct channel *c)
     rt_stop_export(&c->out_req, channel_export_stopped);
 }
 
+static void
+channel_import_request_done_dynamic(struct channel_import_request *req)
+{
+  mb_free(req);
+}
+
 static void
 channel_request_reload(struct channel *c)
 {
@@ -1059,11 +1242,28 @@ channel_request_reload(struct channel *c)
   ASSERT(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)
-    channel_schedule_reload(c);
-  else
-    c->proto->reload_routes(c);
+    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(c->in_req.hook);
+  ASSERT(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 = {
@@ -2655,11 +2855,37 @@ proto_cmd_restart(struct proto *p, uintptr_t arg, int cnt UNUSED)
   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 dir, int cnt UNUSED)
+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);
@@ -2671,7 +2897,7 @@ proto_cmd_reload(struct proto *p, uintptr_t dir, int cnt UNUSED)
     return;
 
   /* All channels must support reload */
-  if (dir != CMD_RELOAD_OUT)
+  if (prr->dir != CMD_RELOAD_OUT)
     WALK_LIST(c, p->channels)
       if ((c->channel_state == CS_UP) && !channel_reloadable(c))
       {
@@ -2682,16 +2908,56 @@ proto_cmd_reload(struct proto *p, uintptr_t dir, int cnt UNUSED)
   log(L_INFO "Reloading protocol %s", p->name);
 
   /* re-importing routes */
-  if (dir != CMD_RELOAD_OUT)
+  if (prr->dir != CMD_RELOAD_OUT)
     WALK_LIST(c, p->channels)
       if (c->channel_state == CS_UP)
-       channel_request_reload(c);
+      {
+        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 (dir != CMD_RELOAD_IN)
+  if (prr->dir != CMD_RELOAD_IN)
     WALK_LIST(c, p->channels)
-      if (c->channel_state == CS_UP)
-       channel_request_feeding_dynamic(c, CFRT_AUXILIARY);
+      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);
 }
index 227201dc83e715bd1285f0d9fc8cbf87b75180c4..7737102805bf205ae95b978b634ef77b6c0e966c 100644 (file)
@@ -125,6 +125,12 @@ struct proto_config {
   /* Protocol-specific data follow... */
 };
 
+struct channel_import_request {
+  struct channel_import_request *next;                 /* Next in request chain */
+  void (*done)(struct channel_import_request *);       /* Called when import finishes */
+  const struct f_trie *trie;                           /* Reload only matching nets */
+};
+
 #define TLIST_PREFIX proto
 #define TLIST_TYPE struct proto
 #define TLIST_ITEM n
@@ -194,7 +200,7 @@ struct proto {
 
   void (*rt_notify)(struct proto *, struct channel *, const net_addr *net, struct rte *new, const struct rte *old);
   int (*preexport)(struct channel *, struct rte *rt);
-  void (*reload_routes)(struct channel *);
+  int (*reload_routes)(struct channel *, struct channel_import_request *cir);
   void (*feed_begin)(struct channel *);
   void (*feed_end)(struct channel *);
 
@@ -280,6 +286,14 @@ struct proto *proto_iterate_named(struct symbol *sym, struct protocol *proto, st
 
 #define PROTO_WALK_CMD(sym,pr,p) for(struct proto *p = NULL; p = proto_iterate_named(sym, pr, p); )
 
+/* Request from CLI to reload multiple protocols */
+struct proto_reload_request {
+  const struct f_trie *trie;   /* Trie to apply */
+  _Atomic uint counter;                /* How many channels remaining */
+  uint dir;                    /* Direction of reload */
+  event ev;                    /* Event to run when finished */
+};
+
 #define PROTO_ENTER_FROM_MAIN(p)    ({ \
     ASSERT_DIE(birdloop_inside(&main_birdloop)); \
     struct birdloop *_loop = (p)->loop; \
@@ -573,6 +587,8 @@ struct channel {
   struct f_trie *refeed_trie;          /* Auxiliary refeed trie */
   struct channel_feeding_request *refeeding;   /* Refeeding the channel */
   struct channel_feeding_request *refeed_pending;      /* Scheduled refeeds */
+  struct channel_import_request *importing;    /* Importing the channel */
+  struct channel_import_request *import_pending;       /* Scheduled imports */
 
   uint feed_block_size;                        /* How many routes to feed at once */
 
@@ -669,7 +685,8 @@ struct channel *proto_add_channel(struct proto *p, struct channel_config *cf);
 int proto_configure_channel(struct proto *p, struct channel **c, struct channel_config *cf);
 
 void channel_set_state(struct channel *c, uint state);
-void channel_schedule_reload(struct channel *c);
+void channel_schedule_reload(struct channel *c, struct channel_import_request *cir);
+int channel_import_request_prefilter(struct channel_import_request *cir_head, const net_addr *n);
 
 static inline void channel_init(struct channel *c) { channel_set_state(c, CS_START); }
 static inline void channel_open(struct channel *c) { channel_set_state(c, CS_UP); }
@@ -678,7 +695,7 @@ static inline void channel_close(struct channel *c) { channel_set_state(c, CS_ST
 struct channel_feeding_request {
   struct channel_feeding_request *next;                        /* Next in request chain */
   void (*done)(struct channel_feeding_request *);      /* Called when refeed finishes */
-  struct f_trie *trie;                                 /* Reload only matching nets */
+  const struct f_trie *trie;                           /* Reload only matching nets */
   PACKED enum channel_feeding_request_type {
     CFRT_DIRECT = 1,                                   /* Refeed by export restart */
     CFRT_AUXILIARY,                                    /* Refeed by auxiliary request */
index bd9f9363eaa34dd85fb0eb9090662d595b03e4c8..65da04a1a856945c822f4d6c53bda7320564b32e 100644 (file)
--- a/nest/rt.h
+++ b/nest/rt.h
@@ -253,6 +253,23 @@ struct rte_storage {
 
 /* Table-channel connections */
 
+struct rt_prefilter {
+  union {
+    const struct f_trie *trie;
+    const net_addr *addr;      /* Network prefilter address */
+    int (*hook)(const struct rt_prefilter *, const net_addr *);
+  };
+                               /* Network prefilter mode (TE_ADDR_*) */
+  enum {
+    TE_ADDR_NONE = 0,          /* No address matching */
+    TE_ADDR_EQUAL,             /* Exact query - show route <addr> */
+    TE_ADDR_FOR,               /* Longest prefix match - show route for <addr> */
+    TE_ADDR_IN,                        /* Interval query - show route in <addr> */
+    TE_ADDR_TRIE,              /* Query defined by trie */
+    TE_ADDR_HOOK,              /* Query processed by supplied custom hook */
+  } mode;
+} PACKED;
+
 struct rt_import_request {
   struct rt_import_hook *hook;         /* The table part of importer */
   char *name;
@@ -300,23 +317,6 @@ struct rt_pending_export {
   u64 seq;                             /* Sequential ID (table-local) of the pending export */
 };
 
-struct rt_prefilter {
-  union {
-    const struct f_trie *trie;
-    const net_addr *addr;      /* Network prefilter address */
-    int (*hook)(const struct rt_prefilter *, const net_addr *);
-  };
-                               /* Network prefilter mode (TE_ADDR_*) */
-  enum {
-    TE_ADDR_NONE = 0,          /* No address matching */
-    TE_ADDR_EQUAL,             /* Exact query - show route <addr> */
-    TE_ADDR_FOR,               /* Longest prefix match - show route for <addr> */
-    TE_ADDR_IN,                        /* Interval query - show route in <addr> */
-    TE_ADDR_TRIE,              /* Query defined by trie */
-    TE_ADDR_HOOK,              /* Query processed by supplied custom hook */
-  } mode;
-} PACKED;
-
 struct rt_export_request {
   struct rt_export_hook *hook;         /* Table part of the export */
   char *name;          /* Network prefilter address */
index f7307b66fc8f362de2208e502afc49741f9a8d43..9ecc9e5431a16831eca1812cc96cf4902d87ab19 100644 (file)
@@ -1904,6 +1904,7 @@ bgp_out_table_feed(void *data)
     {
       switch (hook->h.req->prefilter.mode)
       {
+       case TE_ADDR_HOOK:
        case TE_ADDR_TRIE:
        case TE_ADDR_IN:
          if (!rt_prefilter_net(&hook->h.req->prefilter, n->net))
index 0de9c603f4d3101a722001b9ebe9b3a72fb9b401..bac1af3af24c4a83ed4f03fc428c0b178df2f1c8 100644 (file)
@@ -1560,18 +1560,30 @@ bgp_update_bfd(struct bgp_proto *p, const struct bfd_options *bfd)
   }
 }
 
-static void
-bgp_reload_routes(struct channel *C)
+static int
+bgp_reload_routes(struct channel *C, struct channel_import_request *cir)
 {
   struct bgp_proto *p = (void *) C->proto;
   struct bgp_channel *c = (void *) C;
 
   /* Ignore non-BGP channels */
   if (C->class != &channel_bgp)
-    return;
+  {
+    cir->done(cir);
+    return 1;
+  }
+
+  if (cir->trie)
+  {
+    cir->done(cir);
+    return 0;
+  }
+  /* We do not need cir anymore and later we will not be able to detect when to free it. */
+  cir->done(cir);
 
   ASSERT(p->conn && p->route_refresh);
   bgp_schedule_packet(p->conn, c, PKT_ROUTE_REFRESH);
+  return 1;
 }
 
 static void
index 896bf5a367fdf2101698adf6dafc1c2183991045..c1635afc66081f3d62d1ad7b27a871469a4c4ed0 100644 (file)
 #include "lib/macro.h"
 
 static int ospf_preexport(struct channel *C, rte *new);
-static void ospf_reload_routes(struct channel *C);
+static int ospf_reload_routes(struct channel *C, struct channel_import_request *cir);
 static int ospf_rte_better(const rte *new, const rte *old);
 static u32 ospf_rte_igp_metric(const rte *rt);
 static void ospf_disp(timer *timer);
@@ -432,16 +432,19 @@ ospf_schedule_rtcalc(struct ospf_proto *p)
   p->calcrt = 1;
 }
 
-static void
-ospf_reload_routes(struct channel *C)
+static int
+ospf_reload_routes(struct channel *C, struct channel_import_request *cir)
 {
   struct ospf_proto *p = (struct ospf_proto *) C->proto;
+  cir->next = p->cir;
+  p->cir = cir;
 
   if (p->calcrt == 2)
-    return;
+    return 1;
 
   OSPF_TRACE(D_EVENTS, "Scheduling routing table calculation with route reload");
   p->calcrt = 2;
+  return 1;
 }
 
 
index 3477ba5ab3824e8ecee9e37b35ffdf4c716835ba..aebd04d76326adddf8184bd637ed8a95f6d0b72f 100644 (file)
@@ -219,6 +219,7 @@ struct ospf_proto
   slist lsal;                  /* List of all LSA's */
   int calcrt;                  /* Routing table calculation scheduled?
                                   0=no, 1=normal, 2=forced reload */
+  struct channel_import_request *cir; /* Struct with trie for partial reload */
   list iface_list;             /* List of OSPF interfaces (struct ospf_iface) */
   list area_list;              /* List of OSPF areas (struct ospf_area) */
   int areano;                  /* Number of area I belong to */
index 69c2907d82fddd2ec099cc8a5a9b7bef2ec19532..ffb075d5d37fb163cdde3cff707c49ba0df1165e 100644 (file)
@@ -1700,7 +1700,8 @@ ospf_rt_spf(struct ospf_proto *p)
   rt_sync(p);
   lp_flush(p->nhpool);
 
-  p->calcrt = 0;
+  if (p->cir == NULL)  /* If there is no more cir waiting for reload */
+    p->calcrt = 0;
 }
 
 
@@ -2020,11 +2021,16 @@ rt_sync(struct ospf_proto *p)
 
   OSPF_TRACE(D_EVENTS, "Starting routing table synchronization");
 
+  struct channel_import_request *cir = p->cir;
+  p->cir = NULL;
+
   DBG("Now syncing my rt table with nest's\n");
   FIB_ITERATE_INIT(&fit, fib);
 again1:
   FIB_ITERATE_START(fib, &fit, ort, nf)
   {
+    if (cir && !channel_import_request_prefilter(cir, nf->fn.addr))
+      continue;
     /* Sanity check of next-hop addresses, failure should not happen */
     if (nf->n.type && nf->n.nhs)
     {
@@ -2099,7 +2105,6 @@ again1:
        DBG("Mod rte type %d - %N via %I on iface %s, met %d\n",
            a0.source, nf->fn.addr, a0.gw, a0.iface ? a0.iface->name : "(none)", nf->n.metric1);
            */
-
        rte_update(p->p.main_channel, nf->fn.addr, &e0, p->p.main_source);
       }
     }
@@ -2125,6 +2130,13 @@ again1:
   }
   FIB_ITERATE_END;
 
+  while(cir)
+  {
+    struct channel_import_request *next = cir->next;
+    cir->done(cir);
+    cir = next;
+  }
+
   WALK_LIST(oa, p->area_list)
   {
     /* Cleanup ASBR hash tables */
index 442a951f7b42fd02ab976e1bac7240169d06de4a..1aeba30b5e9823f99284a4e172df35521f690482 100644 (file)
@@ -96,13 +96,37 @@ pipe_preexport(struct channel *C, rte *e)
   return 0;
 }
 
-static void
-pipe_reload_routes(struct channel *C)
+void
+pipe_import_by_refeed_free(struct channel_feeding_request *cfr)
 {
-  struct pipe_proto *p = (void *) C->proto;
+  struct import_to_export_reload *reload = SKIP_BACK(struct import_to_export_reload, cfr, cfr);
+  reload->cir->done(reload->cir);
+}
 
-  /* Route reload on one channel is just refeed on the other */
-  channel_request_feeding_dynamic((C == p->pri) ? p->sec : p->pri, CFRT_DIRECT);
+static int
+pipe_reload_routes(struct channel *C, struct channel_import_request *cir)
+{
+  struct pipe_proto *p = (void *) C->proto;
+  if (cir->trie)
+  {
+    struct import_to_export_reload *reload = lp_alloc(cir->trie->lp, sizeof *reload);
+    *reload = (struct import_to_export_reload) {
+      .cir = cir,
+      .cfr = {
+             .type = CFRT_AUXILIARY,
+             .done = pipe_import_by_refeed_free,
+             .trie = cir->trie,
+           },
+    };
+    channel_request_feeding((C == p->pri) ? p->sec : p->pri, &reload->cfr);
+  }
+  else
+  {
+    /* Route reload on one channel is just refeed on the other */
+    channel_request_feeding_dynamic((C == p->pri) ? p->sec : p->pri, CFRT_DIRECT);
+    cir->done(cir);
+  }
+  return 1;
 }
 
 static void
index 501b85654e0591bc98715214d32d6140d760cc8f..c714f13b434aa9adadbf673d706cb3db69ba2c8a 100644 (file)
@@ -28,3 +28,8 @@ struct pipe_proto {
 #define PIPE_FL_RR_BEGIN_PENDING       1       /* Route refresh should start with the first route notified */
 
 #endif
+
+struct import_to_export_reload {
+  struct channel_import_request *cir;  /* We can not free this struct before reload finishes. */
+  struct channel_feeding_request cfr;  /* New request we actually need - import was changed to feed the other side. */
+};
index e09d082b7622453be12f766bd007eef64fb9c384..50a0751448dc9a9fcd522019d7659d66c5c8ce97 100644 (file)
@@ -968,6 +968,9 @@ rip_timer(timer *t)
 
   FIB_ITERATE_INIT(&fit, &p->rtable);
 
+  struct channel_import_request *cir = p->cir;
+  p->cir = NULL;
+
   loop:
   FIB_ITERATE_START(&p->rtable, &fit, struct rip_entry, en)
   {
@@ -989,14 +992,13 @@ rip_timer(timer *t)
     }
 
     /* Propagating eventual change */
-    if (changed || p->rt_reload)
+    if ((changed || p->rt_reload) && (cir == NULL || channel_import_request_prefilter(cir, en->n.addr)))
     {
       /*
        * We have to restart the iteration because there may be a cascade of
        * synchronous events rip_announce_rte() -> nest table change ->
        * rip_rt_notify() -> p->rtable change, invalidating hidden variables.
        */
-
       FIB_ITERATE_PUT_NEXT(&fit, &p->rtable);
       rip_announce_rte(p, en);
       goto loop;
@@ -1047,7 +1049,19 @@ rip_timer(timer *t)
       }
   }
 
-  tm_start(p->timer, MAX(next - now_, 100 MS));
+  while(cir)
+  {
+    struct channel_import_request *next_cir = cir->next;
+    cir->done(cir);
+    cir = next_cir;
+  }
+  if (p->cir)
+  {
+    p->rt_reload = 1;
+    rip_kick_timer(p);
+  }
+  else
+    tm_start(p->timer, MAX(next - now_, 100 MS));
 }
 
 static inline void
@@ -1148,17 +1162,21 @@ rip_trigger_update(struct rip_proto *p)
  *     RIP protocol glue
  */
 
-static void
-rip_reload_routes(struct channel *C)
+static int
+rip_reload_routes(struct channel *C, struct channel_import_request *cir)
 {
   struct rip_proto *p = (struct rip_proto *) C->proto;
 
+  cir->next = p->cir;
+  p->cir = cir;
+
   if (p->rt_reload)
-    return;
+    return 1;
 
   TRACE(D_EVENTS, "Scheduling route reload");
   p->rt_reload = 1;
   rip_kick_timer(p);
+  return 1;
 }
 
 static struct rte_owner_class rip_rte_owner_class;
index 6d70df46526c81a085f81efff49ffbc2ee22fc95..941efb85cc71fe2e0575c05e060124726cb4f6a3 100644 (file)
@@ -103,6 +103,7 @@ struct rip_proto
 
   struct tbf log_pkt_tbf;              /* TBF for packet messages */
   struct tbf log_rte_tbf;              /* TBF for RTE messages */
+  struct channel_import_request *cir;  /* Trie for partial reload */
 };
 
 struct rip_iface
index 2c3e09f4ba5920c76d4f5f98c9a2c208ce97362d..c31b6268c15e5af44b7017a65589295a88b00fb9 100644 (file)
@@ -172,6 +172,25 @@ static_mark_all(struct static_proto *p)
     ev_schedule(p->event);
 }
 
+static void
+static_mark_partial(struct static_proto *p, struct channel_import_request *cir)
+{
+  struct static_config *cf = (void *) p->p.cf;
+  struct static_route *r;
+
+  WALK_LIST(r, cf->routes)
+    if (r->state == SRS_CLEAN && trie_match_net(cir->trie, r->net))
+    {
+      r->state = SRS_DIRTY;
+      BUFFER_PUSH(p->marked) = r;
+    }
+
+  if (!ev_active(p->event))
+    ev_schedule(p->event);
+
+  cir->done(cir);
+}
+
 
 static void
 static_announce_marked(void *P)
@@ -395,14 +414,19 @@ static_bfd_notify(struct bfd_request *req)
     static_mark_rte(p, r->mp_head);
 }
 
-static void
-static_reload_routes(struct channel *C)
+static int
+static_reload_routes(struct channel *C, struct channel_import_request *cir)
 {
   struct static_proto *p = (void *) C->proto;
 
   TRACE(D_EVENTS, "Scheduling route reload");
 
-  static_mark_all(p);
+  if (cir->trie)
+    static_mark_partial(p, cir);
+  else
+    static_mark_all(p);
+
+  return 1;
 }
 
 static int
index 0187bf0eafbcfd7c328fafb6b36636f6974bb964..4c5afddcbc1343ecd196ac437d90fe2b67d56ec6 100644 (file)
@@ -766,11 +766,18 @@ krt_if_notify(struct proto *P, uint flags, struct iface *iface UNUSED)
     krt_scan_timer_kick(p);
 }
 
-static void
-krt_reload_routes(struct channel *C)
+static int
+krt_reload_routes(struct channel *C, struct channel_import_request *cir)
 {
   struct krt_proto *p = (void *) C->proto;
 
+
+  if (cir->trie)
+  {
+    cir->done(cir);
+    return 0;
+  }
+
   /* Although we keep learned routes in krt_table, we rather schedule a scan */
 
   if (KRT_CF->learn)
@@ -778,6 +785,9 @@ krt_reload_routes(struct channel *C)
     p->reload = 1;
     krt_scan_timer_kick(p);
   }
+
+  cir->done(cir);
+  return 1;
 }
 
 static void krt_cleanup(struct krt_proto *p);