]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Initial commit for new protocol `stats'
authorVojtech Vilimek <vojtech.vilimek@nic.cz>
Wed, 13 Jul 2022 13:39:14 +0000 (15:39 +0200)
committerVojtech Vilimek <vojtech.vilimek@nic.cz>
Wed, 13 Jul 2022 13:39:14 +0000 (15:39 +0200)
The protocol was created as a copy of pipe protocol. Many things left unchanged.

configure.ac
nest/protocol.h
proto/Doc
proto/stats/Doc [new file with mode: 0644]
proto/stats/Makefile [new file with mode: 0644]
proto/stats/config.Y [new file with mode: 0644]
proto/stats/stats.c [new file with mode: 0644]
proto/stats/stats.h [new file with mode: 0644]

index 321bed95a74ec8aa759d8770d447c0602a34dde6..87f0fe75cee08d6836dd58c4351cfb2c94036f60 100644 (file)
@@ -304,7 +304,7 @@ if test "$enable_mpls_kernel" != no ; then
   fi
 fi
 
-all_protocols="$proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static"
+all_protocols="$proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static stats"
 
 all_protocols=`echo $all_protocols | sed 's/ /,/g'`
 
index 026d42ab989f9c5615c5b740c566f2bcf7fb719c..d4c38b907c59ccc74e05f9227d53a3203c3f3de5 100644 (file)
@@ -84,7 +84,8 @@ void protos_dump_all(void);
 extern struct protocol
   proto_device, proto_radv, proto_rip, proto_static, proto_mrt,
   proto_ospf, proto_perf,
-  proto_pipe, proto_bgp, proto_bfd, proto_babel, proto_rpki;
+  proto_pipe, proto_bgp, proto_bfd, proto_babel, proto_rpki,
+  proto_stats;
 
 /*
  *     Routing Protocol Instance
index ef573d2afa64772eae8fdbea75fbec81d4809b93..f5eb5b35e5ef2d5838c28cd41dc7081f2d2f9ee3 100644 (file)
--- a/proto/Doc
+++ b/proto/Doc
@@ -7,5 +7,6 @@ C pipe
 C radv
 C rip
 C rpki
+C stats
 C static
 S ../nest/rt-dev.c
diff --git a/proto/stats/Doc b/proto/stats/Doc
new file mode 100644 (file)
index 0000000..cb89099
--- /dev/null
@@ -0,0 +1 @@
+S stats.c
diff --git a/proto/stats/Makefile b/proto/stats/Makefile
new file mode 100644 (file)
index 0000000..19e972c
--- /dev/null
@@ -0,0 +1,7 @@
+src := stats.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
+$(call proto-build,stats_build)
+
+tests_objs := $(tests_objs) $(src-o-files)
diff --git a/proto/stats/config.Y b/proto/stats/config.Y
new file mode 100644 (file)
index 0000000..cf9a906
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ *     BIRD -- Table-to-Table Protocol Configuration
+ *
+ *     (c) 1999 Martin Mares <mj@ucw.cz>
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+/* old: #include "proto/pipe/pipe.h" */
+#include "proto/stats/stats.h"
+
+CF_DEFINES
+
+/* old: #define PIPE_CFG ((struct pipe_config *) this_proto) */
+#define STATS_CFG ((struct stats_config *) this_proto) 
+
+CF_DECLS
+
+/* TODO here add more keywords */
+/* old: CF_KEYWORDS(PIPE, PEER, TABLE, MAX, GENERATION) */
+CF_KEYWORDS(STATS, PEER, TABLE, MAX, GENERATION)
+
+CF_GRAMMAR
+
+proto: stats_proto '}' { this_channel = NULL; }  ;
+
+stats_proto_start: proto_start STATS
+{
+  this_proto = proto_config_new(&proto_stats, $1);
+  STATS_CFG->max_generation = 16;
+}
+proto_name
+{
+  this_channel = proto_cf_main_channel(this_proto);
+  if (!this_channel) {
+    this_channel = channel_config_new(NULL, NULL, 0, this_proto);
+    this_channel->in_filter = FILTER_ACCEPT;
+    this_channel->out_filter = FILTER_ACCEPT;
+  }
+};
+
+stats_proto:
+   stats_proto_start '{'
+ | stats_proto proto_item ';'
+ | stats_proto channel_item_ ';'
+ | stats_proto IMPORT IN net_any imexport ';' {
+    if (this_channel->net_type && ($4->type != this_channel->net_type))
+      cf_error("Incompatible export prefilter type");
+    STATS_CFG->in_subprefix = $4;
+    this_channel->in_filter = $5;
+  }
+ | stats_proto PEER TABLE rtable ';' { STATS_CFG->peer = $4; }
+ | stats_proto MAX GENERATION expr ';' {
+    if (($4 < 1) || ($4 > 254)) cf_error("Max generation must be in range 1..254, got %u", $4);
+    STATS_CFG->max_generation = $4;
+  }
+ ;
+
+CF_CODE
+
+CF_END
diff --git a/proto/stats/stats.c b/proto/stats/stats.c
new file mode 100644 (file)
index 0000000..c4af70f
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+ *     BIRD -- Table-to-Table Routing Protocol a.k.a Pipe
+ *
+ *     (c) 1999--2000 Martin Mares <mj@ucw.cz>
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: Pipe
+ *
+ * The Pipe protocol is very simple. It just connects to two routing tables
+ * using proto_add_announce_hook() and whenever it receives a rt_notify()
+ * about a change in one of the tables, it converts it to a rte_update()
+ * in the other one.
+ *
+ * To avoid pipe loops, Pipe keeps a `being updated' flag in each routing
+ * table.
+ *
+ * A pipe has two announce hooks, the first connected to the main
+ * table, the second connected to the peer table. When a new route is
+ * announced on the main table, it gets checked by an export filter in
+ * ahook 1, and, after that, it is announced to the peer table via
+ * rte_update(), an import filter in ahook 2 is called. When a new
+ * route is announced in the peer table, an export filter in ahook2
+ * and an import filter in ahook 1 are used. Oviously, there is no
+ * need in filtering the same route twice, so both import filters are
+ * set to accept, while user configured 'import' and 'export' filters
+ * are used as export filters in ahooks 2 and 1. Route limits are
+ * handled similarly, but on the import side of ahooks.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "nest/bird.h"
+#include "nest/iface.h"
+#include "nest/protocol.h"
+#include "nest/rt.h"
+#include "nest/cli.h"
+#include "conf/conf.h"
+#include "filter/filter.h"
+#include "lib/string.h"
+
+#include "stats.h"
+
+#ifdef CONFIG_BGP
+#include "proto/bgp/bgp.h"
+#endif
+
+static void
+stats_rt_notify(struct proto *P, struct channel *src_ch, const net_addr *n, rte *new, const rte *old)
+{
+  struct stats_proto *p = (void *) P;
+  struct channel *dst = (src_ch == p->pri) ? p->sec : p->pri;
+
+  if (!new && !old)
+    return;
+
+  if (new)
+    {
+      rte e0 = {
+       .attrs = new->attrs,
+       .src = new->src,
+       .generation = new->generation + 1,
+      };
+
+      ea_unset_attr(&e0.attrs, 0, &ea_gen_hostentry);
+
+      rte_update(dst, n, &e0, new->src);
+    }
+  else
+    rte_update(dst, n, NULL, old->src);
+}
+
+static int
+stats_preexport(struct channel *c, rte *e)
+{
+  struct stats_proto *p = (void *) c->proto;
+
+  /* Avoid direct loopbacks */
+  if (e->sender == c->in_req.hook)
+    return -1;
+
+  /* Indirection check */
+  uint max_generation = ((struct stats_config *) p->p.cf)->max_generation;
+  if (e->generation >= max_generation)
+  {
+    log_rl(&p->rl_gen, L_ERR "Route overpiped (%u hops of %u configured in %s) in table %s: %N %s/%u:%u",
+       e->generation, max_generation, c->proto->name,
+       c->table->name, e->net, e->src->proto->name, e->src->private_id, e->src->global_id);
+
+    return -1;
+  }
+
+  return 0;
+}
+
+static void
+stats_reload_routes(struct channel *C)
+{
+  struct stats_proto *p = (void *) C->proto;
+
+  /* Route reload on one channel is just refeed on the other */
+  channel_request_feeding((C == p->pri) ? p->sec : p->pri);
+}
+
+
+static void
+stats_postconfig(struct proto_config *CF)
+{
+  struct stats_config *cf = (void *) CF;
+  struct channel_config *cc = proto_cf_main_channel(CF);
+
+  if (!cc->table)
+    cf_error("Primary routing table not specified");
+
+  if (!cf->peer)
+    cf_error("Secondary routing table not specified");
+
+  if (cc->table == cf->peer)
+    cf_error("Primary table and peer table must be different");
+
+  if (cc->table->addr_type != cf->peer->addr_type)
+    cf_error("Primary table and peer table must have the same type");
+
+  if (cc->out_subprefix && (cc->table->addr_type != cc->out_subprefix->type))
+    cf_error("Export subprefix must match table type");
+
+  if (cf->in_subprefix && (cc->table->addr_type != cf->in_subprefix->type))
+    cf_error("Import subprefix must match table type");
+
+  if (cc->rx_limit.action)
+    cf_error("Pipe protocol does not support receive limits");
+
+  if (cc->in_keep)
+    cf_error("Pipe protocol prohibits keeping filtered routes");
+
+  cc->debug = cf->c.debug;
+}
+
+static int
+stats_configure_channels(struct stats_proto *s, struct stats_config *cf)
+{
+  struct channel_config *cc = proto_cf_main_channel(&cf->c);
+
+  struct channel_config pri_cf = {
+    .name = "pri",
+    .channel = cc->channel,
+    .table = cc->table,
+    .out_filter = cc->out_filter,
+    .out_subprefix = cc->out_subprefix,
+    .in_limit = cc->in_limit,
+    .ra_mode = RA_ANY,
+    .debug = cc->debug,
+    .rpki_reload = cc->rpki_reload,
+  };
+
+  struct channel_config sec_cf = {
+    .name = "sec",
+    .channel = cc->channel,
+    .table = cf->peer,
+    .out_filter = cc->in_filter,
+    .out_subprefix = cf->in_subprefix,
+    .in_limit = cc->out_limit,
+    .ra_mode = RA_ANY,
+    .debug = cc->debug,
+    .rpki_reload = cc->rpki_reload,
+  };
+
+  return
+    proto_configure_channel(&s->p, &s->pri, &pri_cf) &&
+    proto_configure_channel(&s->p, &s->sec, &sec_cf);
+}
+
+static struct proto *
+stats_init(struct proto_config *CF)
+{
+  struct proto *P = proto_new(CF);
+  struct stats_proto *p = (void *) P;
+  struct stats_config *cf = (void *) CF;
+
+  P->rt_notify = stats_rt_notify;
+  P->preexport = stats_preexport;
+  P->reload_routes = stats_reload_routes;
+
+  p->rl_gen = (struct tbf) TBF_DEFAULT_LOG_LIMITS;
+
+  stats_configure_channels(p, cf);
+
+  return P;
+}
+
+static int
+stats_reconfigure(struct proto *P, struct proto_config *CF)
+{
+  struct stats_proto *p = (void *) P;
+  struct stats_config *cf = (void *) CF;
+
+  return stats_configure_channels(p, cf);
+}
+
+static void
+stats_copy_config(struct proto_config *dest UNUSED, struct proto_config *src UNUSED)
+{
+  /* Just a shallow copy, not many items here */
+}
+
+static void
+stats_get_status(struct proto *P, byte *buf)
+{
+  struct stats_proto *p = (void *) P;
+
+  bsprintf(buf, "%s <=> %s", p->pri->table->name, p->sec->table->name);
+}
+
+static void
+stats_show_stats(struct stats_proto *p)
+{
+  struct channel_import_stats *s1i = &p->pri->import_stats;
+  struct channel_export_stats *s1e = &p->pri->export_stats;
+  struct channel_import_stats *s2i = &p->sec->import_stats;
+  struct channel_export_stats *s2e = &p->sec->export_stats;
+
+  struct rt_import_stats *rs1i = p->pri->in_req.hook ? &p->pri->in_req.hook->stats : NULL;
+  struct rt_export_stats *rs1e = p->pri->out_req.hook ? &p->pri->out_req.hook->stats : NULL;
+  struct rt_import_stats *rs2i = p->sec->in_req.hook ? &p->sec->in_req.hook->stats : NULL;
+  struct rt_export_stats *rs2e = p->sec->out_req.hook ? &p->sec->out_req.hook->stats : NULL;
+
+  u32 pri_routes = p->pri->in_limit.count;
+  u32 sec_routes = p->sec->in_limit.count;
+
+  /*
+   * Pipe stats (as anything related to pipes) are a bit tricky. There
+   * are two sets of stats - s1 for ahook to the primary routing and
+   * s2 for the ahook to the secondary routing table. The user point
+   * of view is that routes going from the primary routing table to
+   * the secondary routing table are 'exported', while routes going in
+   * the other direction are 'imported'.
+   *
+   * Each route going through a pipe is, technically, first exported
+   * to the pipe and then imported from that pipe and such operations
+   * are counted in one set of stats according to the direction of the
+   * route propagation. Filtering is done just in the first part
+   * (export). Therefore, we compose stats for one directon for one
+   * user direction from both import and export stats, skipping
+   * immediate and irrelevant steps (exp_updates_accepted,
+   * imp_updates_received, imp_updates_filtered, ...).
+   *
+   * Rule of thumb is that stats s1 have the correct 'polarity'
+   * (imp/exp), while stats s2 have switched 'polarity'.
+   */
+
+  cli_msg(-1006, "  Routes:         %u imported, %u exported",
+         pri_routes, sec_routes);
+  cli_msg(-1006, "  Route change stats:     received   rejected   filtered    ignored   accepted");
+  cli_msg(-1006, "    Import updates:     %10u %10u %10u %10u %10u",
+         rs2e->updates_received, s2e->updates_rejected + s1i->updates_invalid,
+         s2e->updates_filtered, rs1i->updates_ignored, rs1i->updates_accepted);
+  cli_msg(-1006, "    Import withdraws:   %10u %10u        --- %10u %10u",
+         rs2e->withdraws_received, s1i->withdraws_invalid,
+         rs1i->withdraws_ignored, rs1i->withdraws_accepted);
+  cli_msg(-1006, "    Export updates:     %10u %10u %10u %10u %10u",
+         rs1e->updates_received, s1e->updates_rejected + s2i->updates_invalid,
+         s1e->updates_filtered, rs2i->updates_ignored, rs2i->updates_accepted);
+  cli_msg(-1006, "    Export withdraws:   %10u %10u        --- %10u %10u",
+         rs1e->withdraws_received, s2i->withdraws_invalid,
+         rs2i->withdraws_ignored, rs2i->withdraws_accepted);
+}
+
+static void
+stats_show_proto_info(struct proto *P)
+{
+  struct stats_proto *p = (void *) P;
+
+  cli_msg(-1006, "  Channel %s", "main");
+  cli_msg(-1006, "    Table:          %s", p->pri->table->name);
+  cli_msg(-1006, "    Peer table:     %s", p->sec->table->name);
+  cli_msg(-1006, "    Import state:   %s", rt_export_state_name(rt_export_get_state(p->sec->out_req.hook)));
+  cli_msg(-1006, "    Export state:   %s", rt_export_state_name(rt_export_get_state(p->pri->out_req.hook)));
+  cli_msg(-1006, "    Import filter:  %s", filter_name(p->sec->out_filter));
+  cli_msg(-1006, "    Export filter:  %s", filter_name(p->pri->out_filter));
+
+
+
+  channel_show_limit(&p->pri->in_limit, "Import limit:",
+      (p->pri->limit_active & (1 << PLD_IN)), p->pri->limit_actions[PLD_IN]);
+  channel_show_limit(&p->sec->in_limit, "Export limit:",
+      (p->sec->limit_active & (1 << PLD_IN)), p->sec->limit_actions[PLD_IN]);
+
+  if (P->proto_state != PS_DOWN)
+    stats_show_stats(p);
+}
+
+void
+stats_update_debug(struct proto *P)
+{
+  struct stats_proto *p = (void *) P;
+
+  p->pri->debug = p->sec->debug = p->p.debug;
+}
+
+
+struct protocol proto_stats = {
+  .name =              "Stats",
+  .template =          "stat%d",
+  .proto_size =                sizeof(struct stats_proto),
+  .config_size =       sizeof(struct stats_config),
+  .postconfig =                stats_postconfig,
+  .init =              stats_init,
+  .reconfigure =       stats_reconfigure,
+  .copy_config =       stats_copy_config,
+  .get_status =        stats_get_status,
+  .show_proto_info =   stats_show_proto_info
+};
+
+void
+stats_build(void)
+{
+  proto_build(&proto_stats);
+}
diff --git a/proto/stats/stats.h b/proto/stats/stats.h
new file mode 100644 (file)
index 0000000..2634e51
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ *     BIRD -- Table-to-Table Routing Protocol a.k.a Pipe
+ *
+ *     (c) 1999 Martin Mares <mj@ucw.cz>
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_STATS_H_
+#define _BIRD_STATS_H_
+
+struct stats_config {
+  struct proto_config c;
+  struct rtable_config *peer;          /* Table we're connected to */
+  const net_addr *in_subprefix;
+  u8 max_generation;
+};
+
+struct stats_proto {
+  struct proto p;
+  struct channel *pri;
+  struct channel *sec;
+  struct tbf rl_gen;
+};
+
+#endif