]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Basic route aggregation
authorIgor Putovny <igor.putovny@nic.cz>
Wed, 21 Jun 2023 11:15:07 +0000 (13:15 +0200)
committerOndrej Zajicek <santiago@crfreenet.org>
Tue, 26 Sep 2023 13:46:24 +0000 (15:46 +0200)
Add a new protocol offering route aggregation.

User can specify list of route attributes in the configuration file and
run route aggregation on the export side of the pipe protocol. Routes are
sorted and for every group of equivalent routes new route is created and
exported to the routing table. It is also possible to specify filter
which will run for every route before aggregation.

Furthermore, it will be possible to set attributes of new routes
according to attributes of the aggregated routes.

This is a work in progress.

Original work by Igor Putovny, subsequent cleanups and finalization by
Maria Matejka.

23 files changed:
conf/conf.h
conf/confbase.Y
configure.ac
filter/config.Y
filter/data.c
filter/data.h
filter/f-inst.c
filter/filter.c
filter/filter.h
nest/a-path.c
nest/attrs.h
nest/proto.c
nest/protocol.h
nest/route.h
nest/rt-attr.c
nest/rt-table.c
proto/aggregator/Doc [new file with mode: 0644]
proto/aggregator/Makefile [new file with mode: 0644]
proto/aggregator/aggregator.c [new file with mode: 0644]
proto/aggregator/aggregator.h [new file with mode: 0644]
proto/aggregator/config.Y [new file with mode: 0644]
proto/aggregator/test.conf [new file with mode: 0644]
proto/static/static.c

index 486499ad10a87ebbbb8ee68e18a548fec88b7a89..b07b417c2f3137eb3e844171d74e87951cc0428a 100644 (file)
@@ -238,6 +238,9 @@ struct symbol *cf_new_symbol(struct sym_scope *scope, pool *p, struct linpool *l
     sym_->var_ = def_; \
     sym_; })
 
+#define cf_create_symbol(conf_, name_, type_, var_, def_) \
+  cf_define_symbol(conf_, cf_get_symbol(conf_, name_), type_, var_, def_)
+
 void cf_push_scope(struct config *, struct symbol *);
 void cf_pop_scope(struct config *);
 void cf_push_soft_scope(struct config *);
index 0364bc6e18855df12365a8d6cec018fcf607e6a4..69a7676c8be829b60a19f45a3ee19e30239be60f 100644 (file)
@@ -95,6 +95,7 @@ CF_DECLS
   struct timeformat *tf;
   mpls_label_stack *mls;
   const struct adata *bs;
+  struct aggr_item_node *ai;
 }
 
 %token END CLI_MARKER INVALID_TOKEN ELSECOL DDOT
index c9c52038dc2b3e0699fdc45f029ee5cb4877a73c..a7cf0a5d20daa3a2f781526f4deeed082b1d501f 100644 (file)
@@ -312,7 +312,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="aggregator $proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static"
 
 all_protocols=`echo $all_protocols | sed 's/ /,/g'`
 
@@ -320,6 +320,7 @@ if test "$with_protocols" = all ; then
   with_protocols="$all_protocols"
 fi
 
+AH_TEMPLATE([CONFIG_AGGREGATOR],[Aggregator protocol])
 AH_TEMPLATE([CONFIG_BABEL],    [Babel protocol])
 AH_TEMPLATE([CONFIG_BFD],      [BFD protocol])
 AH_TEMPLATE([CONFIG_BGP],      [BGP protocol])
index dfabddf7bff52443cd011b0b8623f99ab4be76b0..a15683f53d98e83a86aa3e7641ec995675e41329 100644 (file)
@@ -45,7 +45,7 @@ static inline void f_method_call_start(struct f_inst *object)
     .object = object,
     .main = new_config->current_scope,
     .scope = {
-      .next = NULL,
+      .next = global_root_scope,
       .hash = scope->hash,
       .active = 1,
       .block = 1,
@@ -244,6 +244,25 @@ f_new_lc_item(u32 f1, u32 t1, u32 f2, u32 t2, u32 f3, u32 t3)
   return t;
 }
 
+static inline struct f_inst *
+f_const_empty(enum f_type t)
+{
+  switch (t) {
+    case T_PATH:
+    case T_CLIST:
+    case T_ECLIST:
+    case T_LCLIST:
+      return f_new_inst(FI_CONSTANT, (struct f_val) {
+       .type = t,
+       .val.ad = &null_adata,
+      });
+    case T_ROUTE:
+      return f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_ROUTE });
+    default:
+      return f_new_inst(FI_CONSTANT, (struct f_val) {});
+  }
+}
+
 /*
  * Remove all new lines and doubled whitespaces
  * and convert all tabulators to spaces
@@ -303,8 +322,8 @@ f_lval_getter(struct f_lval *lval)
 {
   switch (lval->type) {
     case F_LVAL_VARIABLE:      return f_new_inst(FI_VAR_GET, lval->sym);
-    case F_LVAL_SA:            return f_new_inst(FI_RTA_GET, lval->sa);
-    case F_LVAL_EA:            return f_new_inst(FI_EA_GET, lval->da);
+    case F_LVAL_SA:            return f_new_inst(FI_RTA_GET, lval->rte, lval->sa);
+    case F_LVAL_EA:            return f_new_inst(FI_EA_GET, lval->rte, lval->da);
     default:                   bug("Unknown lval type");
   }
 }
@@ -447,6 +466,7 @@ type:
  | CLIST { $$ = T_CLIST; }
  | ECLIST { $$ = T_ECLIST; }
  | LCLIST { $$ = T_LCLIST; }
+ | ROUTE { $$ = T_ROUTE; }
  | type SET {
        switch ($1) {
          case T_INT:
@@ -832,7 +852,7 @@ symbol_value: symbol_known
        $$ = f_new_inst(FI_VAR_GET, $1);
        break;
       case SYM_ATTRIBUTE:
-       $$ = f_new_inst(FI_EA_GET, *$1->attribute);
+       $$ = f_new_inst(FI_EA_GET, f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_ROUTE, .val.rte = NULL }), *$1->attribute);
        break;
       default:
        cf_error("Can't get value of symbol %s", $1->name);
@@ -866,6 +886,16 @@ method_name_cont:
    } '(' var_list ')' {
      $$ = f_dispatch_method($1, FM.object, $4, 1);
    }
+ | static_attr {
+     if (FM.object->type != T_ROUTE)
+       cf_error("Getting a route attribute from %s, need a route", f_type_name(FM.object->type));
+     $$ = f_new_inst(FI_RTA_GET, FM.object, $1);
+   }
+ | dynamic_attr {
+     if (FM.object->type != T_ROUTE)
+       cf_error("Getting a route attribute from %s, need a route", f_type_name(FM.object->type));
+     $$ = f_new_inst(FI_EA_GET, FM.object, $1);
+   }
  ;
 
 term:
@@ -891,9 +921,9 @@ term:
  | constant { $$ = $1; }
  | constructor { $$ = $1; }
 
- | static_attr { $$ = f_new_inst(FI_RTA_GET, $1); }
+ | static_attr { $$ = f_new_inst(FI_RTA_GET, f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_ROUTE, .val.rte = NULL }), $1); }
 
- | dynamic_attr { $$ = f_new_inst(FI_EA_GET, $1); }
+ | dynamic_attr { $$ = f_new_inst(FI_EA_GET, f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_ROUTE, .val.rte = NULL }), $1); }
 
  | term_dot_method
 
@@ -1044,16 +1074,17 @@ lvalue:
      switch ($1->class)
      {
        case SYM_VARIABLE_RANGE:
-        $$ = (struct f_lval) { .type = F_LVAL_VARIABLE, .sym = $1 };
+        $$ = (struct f_lval) { .type = F_LVAL_VARIABLE, .sym = $1, .rte = f_const_empty(T_ROUTE) };
          break;
        case SYM_ATTRIBUTE:
-         $$ = (struct f_lval) { .type = F_LVAL_EA, .da = *($1->attribute) };
+         $$ = (struct f_lval) { .type = F_LVAL_EA, .da = *($1->attribute), .rte = f_const_empty(T_ROUTE) };
         break;
        default:
         cf_error("Variable name or custom attribute name required");
      }
    }
- | static_attr { $$ = (struct f_lval) { .type = F_LVAL_SA, .sa = $1 }; }
- | dynamic_attr { $$ = (struct f_lval) { .type = F_LVAL_EA, .da = $1 }; };
+ | static_attr { $$ = (struct f_lval) { .type = F_LVAL_SA, .sa = $1, .rte = f_const_empty(T_ROUTE) }; }
+ | dynamic_attr { $$ = (struct f_lval) { .type = F_LVAL_EA, .da = $1, .rte = f_const_empty(T_ROUTE) }; }
+ ;
 
 CF_END
index 89b75e5654dec620ed0348152ab0329d3dbf59fd..e268a8ec30aaf274468b05db4fe625b3e5142bcb 100644 (file)
@@ -56,6 +56,9 @@ static const char * const f_type_str[] = {
   [T_LC]       = "lc",
   [T_LCLIST]   = "lclist",
   [T_RD]       = "rd",
+
+  [T_ROUTE] = "route",
+  [T_ROUTES_BLOCK] = "block of routes",
 };
 
 const char *
@@ -78,6 +81,7 @@ f_type_element_type(enum f_type t)
     case T_CLIST:  return T_PAIR;
     case T_ECLIST: return T_EC;
     case T_LCLIST: return T_LC;
+    case T_ROUTES_BLOCK: return T_ROUTE;
     default: return T_VOID;
   };
 }
@@ -206,6 +210,11 @@ val_compare(const struct f_val *v1, const struct f_val *v2)
     return net_compare(v1->val.net, v2->val.net);
   case T_STRING:
     return strcmp(v1->val.s, v2->val.s);
+  case T_PATH:
+    return as_path_compare(v1->val.ad, v2->val.ad);
+  case T_ROUTE:
+  /* Fall through */
+  case T_ROUTES_BLOCK:
   default:
     return F_CMP_ERROR;
   }
@@ -296,6 +305,10 @@ val_same(const struct f_val *v1, const struct f_val *v2)
     return same_tree(v1->val.t, v2->val.t);
   case T_PREFIX_SET:
     return trie_same(v1->val.ti, v2->val.ti);
+  case T_ROUTE:
+    return v1->val.rte == v2->val.rte;
+  case T_ROUTES_BLOCK:
+    return v1->val.ad == v2->val.ad;
   default:
     bug("Invalid type in val_same(): %x", v1->type);
   }
@@ -569,6 +582,36 @@ val_in_range(const struct f_val *v1, const struct f_val *v2)
   return F_CMP_ERROR;
 }
 
+/*
+ * rte_format - format route information
+ */
+static void
+rte_format(const struct rte *rte, buffer *buf)
+{
+  if (rte)
+    buffer_print(buf, "Route [%d] to %N from %s.%s via %s",
+                 rte->src->global_id, rte->net->n.addr,
+                 rte->sender->proto->name, rte->sender->name,
+                 rte->src->proto->name);
+  else
+    buffer_puts(buf, "[No route]");
+}
+
+static void
+rte_block_format(const struct rte *rte, buffer *buf)
+{
+  buffer_print(buf, "Block of routes:");
+
+  int i = 0;
+  while (rte)
+  {
+    buffer_print(buf, "%s%d: ", i ? "; " : " ", i);
+    rte_format(rte, buf);
+    rte = rte->next;
+    i++;
+  }
+}
+
 /*
  * val_format - format filter value
  */
@@ -598,6 +641,8 @@ val_format(const struct f_val *v, buffer *buf)
   case T_ECLIST: ec_set_format(v->val.ad, -1, buf2, 1000); buffer_print(buf, "(eclist %s)", buf2); return;
   case T_LCLIST: lc_set_format(v->val.ad, -1, buf2, 1000); buffer_print(buf, "(lclist %s)", buf2); return;
   case T_PATH_MASK: pm_format(v->val.path_mask, buf); return;
+  case T_ROUTE: rte_format(v->val.rte, buf); return;
+  case T_ROUTES_BLOCK: rte_block_format(v->val.rte, buf); return;
   default:     buffer_print(buf, "[unknown type %x]", v->type); return;
   }
 }
index 3430455a55c5387bebfad646b29fbdb15ed30453..0a521ec5b28dbeef4a03f80539aaa5ad62f08b69 100644 (file)
@@ -11,6 +11,7 @@
 #define _BIRD_FILTER_DATA_H_
 
 #include "nest/bird.h"
+#include "nest/route.h"
 
 /* Type numbers must be in 0..0xff range */
 #define T_MASK 0xff
@@ -62,6 +63,8 @@ enum f_type {
   T_PATH_MASK_ITEM = 0x2b,     /* Path mask item for path mask constructors */
   T_BYTESTRING = 0x2c,
 
+  T_ROUTE = 0x78,
+  T_ROUTES_BLOCK = 0x79,
   T_SET = 0x80,
   T_PREFIX_SET = 0x81,
 } PACKED;
@@ -90,6 +93,7 @@ struct f_val {
     const struct adata *ad;
     const struct f_path_mask *path_mask;
     struct f_path_mask_item pmi;
+    struct rte *rte;
   } val;
 };
 
@@ -136,6 +140,7 @@ enum f_lval_type {
 /* Filter l-value */
 struct f_lval {
   enum f_lval_type type;
+  struct f_inst *rte;
   union {
     struct symbol *sym;
     struct f_dynamic_attr da;
index a7bec81ed758331419f594f565b60deb9c8c86ae..4356a735f9c227a09d3eca681fcddfad6a093424 100644 (file)
     METHOD_CONSTRUCTOR("!for_next");
   }
 
+  INST(FI_ROUTES_BLOCK_FOR_NEXT, 3, 0) {
+    NEVER_CONSTANT;
+    ARG(1, T_ROUTES_BLOCK);
+    if (!v2.type)
+      v2 = v1;
+
+    if (v2.val.rte)
+    {
+      v3.val.rte = v2.val.rte;
+      v2.val.rte = v2.val.rte->next;
+      LINE(2,0);
+    }
+
+    METHOD_CONSTRUCTOR("!for_next");
+  }
+
   INST(FI_CONDITION, 1, 0) {
     ARG(1, T_BOOL);
     if (v1.val.i)
     }
   }
 
-  INST(FI_RTA_GET, 0, 1) {
+  INST(FI_RTA_GET, 1, 1) {
     {
-      STATIC_ATTR;
       ACCESS_RTE;
-      struct rta *rta = (*fs->rte)->attrs;
+      ARG(1, T_ROUTE);
+      STATIC_ATTR;
+
+      struct rta *rta = v1.val.rte ? v1.val.rte->attrs : (*fs->rte)->attrs;
 
       switch (sa.sa_code)
       {
     }
   }
 
-  INST(FI_EA_GET, 0, 1) {      /* Access to extended attributes */
-    DYNAMIC_ATTR;
+  INST(FI_EA_GET, 1, 1) {      /* Access to extended attributes */
     ACCESS_RTE;
     ACCESS_EATTRS;
+    ARG(1, T_ROUTE);
+    DYNAMIC_ATTR;
     RESULT_TYPE(da.f_type);
     {
-      eattr *e = ea_find(*fs->eattrs, da.ea_code);
+      struct ea_list *eal = v1.val.rte ? v1.val.rte->attrs->eattrs : *fs->eattrs;
+      eattr *e = ea_find(eal, da.ea_code);
 
       if (!e) {
        RESULT_VAL(val_empty(da.f_type));
index 65fb92a4122a2b1cd987c7ed57c9d0c3a4ee4c47..560778a846c73bb5ccb1a2d92e175e49316077b1 100644 (file)
@@ -90,6 +90,9 @@ struct filter_state {
   /* Buffer for log output */
   struct buffer buf;
 
+  /* Pointers to routes we are aggregating */
+  const struct f_val *val;
+
   /* Filter execution flags */
   int flags;
 };
@@ -157,18 +160,20 @@ static struct tbf rl_runtime_err = TBF_DEFAULT_LOG_LIMITS;
  * TWOARGS macro to get both of them evaluated.
  */
 static enum filter_return
-interpret(struct filter_state *fs, const struct f_line *line, struct f_val *val)
+interpret(struct filter_state *fs, const struct f_line *line, uint argc, const struct f_val *argv, struct f_val *val)
 {
   /* No arguments allowed */
-  ASSERT(line->args == 0);
+  ASSERT_DIE(line->args == argc);
 
   /* Initialize the filter stack */
   struct filter_stack *fstk = fs->stack;
 
-  fstk->vcnt = line->vars;
-  memset(fstk->vstk, 0, sizeof(struct f_val) * line->vars);
+  /* Set the arguments and top-level variables */
+  fstk->vcnt = line->vars + line->args;
+  memcpy(fstk->vstk, argv, sizeof(struct f_val) * line->args);
+  memset(fstk->vstk + line->args, 0, sizeof(struct f_val) * line->vars);
 
-  /* The same as with the value stack. Not resetting the stack for performance reasons. */
+  /* The same as with the value stack. Not resetting the stack completely for performance reasons. */
   fstk->ecnt = 1;
   fstk->estk[0].line = line;
   fstk->estk[0].pos = 0;
@@ -237,7 +242,6 @@ interpret(struct filter_state *fs, const struct f_line *line, struct f_val *val)
   return F_ERROR;
 }
 
-
 /**
  * f_run - run a filter for a route
  * @filter: filter to run
@@ -271,6 +275,12 @@ f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, i
   if (filter == FILTER_REJECT)
     return F_REJECT;
 
+  return f_run_args(filter, rte, tmp_pool, 0, NULL, flags);
+}
+
+enum filter_return
+f_run_args(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, uint argc, const struct f_val *argv, int flags)
+{
   int rte_cow = ((*rte)->flags & REF_COW);
   DBG( "Running filter `%s'...", filter->name );
 
@@ -285,7 +295,7 @@ f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, i
   LOG_BUFFER_INIT(filter_state.buf);
 
   /* Run the interpreter itself */
-  enum filter_return fret = interpret(&filter_state, filter->root, NULL);
+  enum filter_return fret = interpret(&filter_state, filter->root, argc, argv, NULL);
 
   if (filter_state.old_rta) {
     /*
@@ -337,7 +347,7 @@ f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, i
  */
 
 enum filter_return
-f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool)
+f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool, uint argc, const struct f_val *argv, struct f_val *pres)
 {
   filter_state = (struct filter_state) {
     .stack = &filter_stack,
@@ -347,10 +357,7 @@ f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool
 
   LOG_BUFFER_INIT(filter_state.buf);
 
-  ASSERT(!((*rte)->flags & REF_COW));
-  ASSERT(!rta_is_cached((*rte)->attrs));
-
-  return interpret(&filter_state, expr, NULL);
+  return interpret(&filter_state, expr, argc, argv, pres);
 }
 
 /*
@@ -369,7 +376,7 @@ f_eval(const struct f_line *expr, struct linpool *tmp_pool, struct f_val *pres)
 
   LOG_BUFFER_INIT(filter_state.buf);
 
-  enum filter_return fret = interpret(&filter_state, expr, pres);
+  enum filter_return fret = interpret(&filter_state, expr, 0, NULL, pres);
   return fret;
 }
 
index 91de696cfd7be9a6a42e853f45e80be510a605d4..18ff0874f56f35ddb9175d31263959ad56199cfa 100644 (file)
@@ -52,7 +52,8 @@ struct filter {
 struct rte;
 
 enum filter_return f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, int flags);
-enum filter_return f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool);
+enum filter_return f_run_args(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, uint argc, const struct f_val *argv, int flags);
+enum filter_return f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool, uint argc, const struct f_val *argv, struct f_val *pres);
 enum filter_return f_eval_buf(const struct f_line *expr, struct linpool *tmp_pool, buffer *buf);
 
 struct f_val cf_eval(const struct f_inst *inst, int type);
index c421b41f71519ea2472aab67afd16a78fce1e2e3..aba2c86df0005f7e5e7e4cbd4396fa629587d62c 100644 (file)
@@ -669,6 +669,29 @@ as_path_filter(struct linpool *pool, const struct adata *path, const struct f_va
   return res;
 }
 
+int
+as_path_compare(const struct adata *path1, const struct adata *path2)
+{
+  uint pos1 = 0;
+  uint pos2 = 0;
+  uint val1 = 0;
+  uint val2 = 0;
+
+  while (1)
+  {
+    int res1 = as_path_walk(path1, &pos1, &val1);
+    int res2 = as_path_walk(path2, &pos2, &val2);
+
+    if (res1 == 0 && res2 == 0)
+      return 0;
+
+    if (val1 == val2)
+      continue;
+
+    return val1 < val2 ? -1 : 1;
+  }
+}
+
 int
 as_path_walk(const struct adata *path, uint *pos, uint *val)
 {
index fcd5ac16a31b07af4fff8de3a898055894f473d9..aee3468bc53acd4abfd4851b51316f92982b5bf0 100644 (file)
@@ -51,6 +51,7 @@ u32 as_path_get_last_nonaggregated(const struct adata *path);
 int as_path_contains(const struct adata *path, u32 as, int min);
 int as_path_match_set(const struct adata *path, const struct f_tree *set);
 const struct adata *as_path_filter(struct linpool *pool, const struct adata *path, const struct f_val *set, int pos);
+int as_path_compare(const struct adata *path1, const struct adata *path2);
 int as_path_walk(const struct adata *path, uint *pos, uint *val);
 
 static inline struct adata *as_path_prepend(struct linpool *pool, const struct adata *path, u32 as)
@@ -238,6 +239,7 @@ int lc_set_max(const struct adata *list, lcomm *val);
 int int_set_walk(const struct adata *list, uint *pos, u32 *val);
 int ec_set_walk(const struct adata *list, uint *pos, u64 *val);
 int lc_set_walk(const struct adata *list, uint *pos, lcomm *val);
+int rte_set_walk(const struct adata *list, u32 *pos, struct rte **val);
 
 void ec_set_sort_x(struct adata *set); /* Sort in place */
 
index bc7b7cc820edf94a78e37c230b10fe2f0348c5ff..48ffade5d2dafead352cb40920d9efeacedcee68 100644 (file)
@@ -90,7 +90,6 @@ proto_log_state_change(struct proto *p)
     p->last_state_name_announced = NULL;
 }
 
-
 struct channel_config *
 proto_cf_find_channel(struct proto_config *pc, uint net_type)
 {
index dad0b781bd8c2431fa9a92c5c14eec92ea80c113..596e810e7d7bd476bc4827fc26d2e8e488525ee4 100644 (file)
@@ -39,6 +39,7 @@ struct symbol;
 
 enum protocol_class {
   PROTOCOL_NONE,
+  PROTOCOL_AGGREGATOR,
   PROTOCOL_BABEL,
   PROTOCOL_BFD,
   PROTOCOL_BGP,
@@ -103,7 +104,7 @@ void protos_dump_all(void);
 
 extern struct protocol
   proto_device, proto_radv, proto_rip, proto_static, proto_mrt,
-  proto_ospf, proto_perf,
+  proto_ospf, proto_perf, proto_aggregator,
   proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki;
 
 /*
index 7aec711794179dac68c89e89618ca0690e5ac24a..feb1fa60d039989dba18290a3c00abf25e24dec3 100644 (file)
@@ -342,6 +342,8 @@ void rt_prune_sync(rtable *t, int all);
 int rte_update_out(struct channel *c, const net_addr *n, rte *new, rte *old0, int refeed);
 struct rtable_config *rt_new_table(struct symbol *s, uint addr_type);
 
+int rte_same(rte *x, rte *y);
+
 static inline int rt_is_ip(rtable *tab)
 { return (tab->addr_type == NET_IP4) || (tab->addr_type == NET_IP6); }
 
@@ -474,7 +476,8 @@ typedef struct rta {
 #define RTS_BABEL 13                   /* Babel route */
 #define RTS_RPKI 14                    /* Route Origin Authorization */
 #define RTS_PERF 15                    /* Perf checker */
-#define RTS_MAX 16
+#define RTS_AGGREGATED 16              /* Aggregated route */
+#define RTS_MAX 17
 
 #define RTD_NONE 0                     /* Undefined next hop */
 #define RTD_UNICAST 1                  /* Next hop is neighbor router */
index d793c72e1ef8473e7f78ef75807af5697abc63fa..b341ff46381bfa6a758ae4ff407c14759634aabf 100644 (file)
@@ -75,6 +75,8 @@ const char * const rta_src_names[RTS_MAX] = {
   [RTS_PIPE]           = "pipe",
   [RTS_BABEL]          = "Babel",
   [RTS_RPKI]           = "RPKI",
+  [RTS_PERF]           = "Perf",
+  [RTS_AGGREGATED]     = "aggregated",
 };
 
 const char * rta_dest_names[RTD_MAX] = {
@@ -1272,7 +1274,8 @@ rta_dump(rta *a)
   static char *rts[] = { "", "RTS_STATIC", "RTS_INHERIT", "RTS_DEVICE",
                         "RTS_STAT_DEV", "RTS_REDIR", "RTS_RIP",
                         "RTS_OSPF", "RTS_OSPF_IA", "RTS_OSPF_EXT1",
-                        "RTS_OSPF_EXT2", "RTS_BGP", "RTS_PIPE", "RTS_BABEL" };
+                        "RTS_OSPF_EXT2", "RTS_BGP", "RTS_PIPE", "RTS_BABEL",
+                        "RTS_RPKI", "RTS_PERF", "RTS_AGGREGATED", };
   static char *rtd[] = { "", " DEV", " HOLE", " UNREACH", " PROHIBIT" };
 
   debug("pref=%d uc=%d %s %s%s h=%04x",
index 742e2f05b38b865284abc08afd7ed41089cf401d..8b41ffee11fb5ead5b3a0898332d76fb592d4c00 100644 (file)
 pool *rt_table_pool;
 
 static slab *rte_slab;
-static linpool *rte_update_pool;
+linpool *rte_update_pool;
 
 list routing_tables;
 
@@ -975,7 +975,6 @@ rt_export_merged(struct channel *c, net *net, rte **rt_free, linpool *pool, int
   return best;
 }
 
-
 static void
 rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed,
                 rte *new_best, rte *old_best, int refeed)
@@ -1206,7 +1205,7 @@ rte_free_quick(rte *e)
   sl_free(e);
 }
 
-static int
+int
 rte_same(rte *x, rte *y)
 {
   /* rte.flags / rte.pflags are not checked, as they are internal to rtable */
diff --git a/proto/aggregator/Doc b/proto/aggregator/Doc
new file mode 100644 (file)
index 0000000..6111f2f
--- /dev/null
@@ -0,0 +1 @@
+S aggregator.c
diff --git a/proto/aggregator/Makefile b/proto/aggregator/Makefile
new file mode 100644 (file)
index 0000000..d1dae8d
--- /dev/null
@@ -0,0 +1,6 @@
+src := aggregator.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
+
+tests_objs := $(tests_objs) $(src-o-files)
diff --git a/proto/aggregator/aggregator.c b/proto/aggregator/aggregator.c
new file mode 100644 (file)
index 0000000..9d95c7d
--- /dev/null
@@ -0,0 +1,731 @@
+/*
+ *     BIRD Internet Routing Daemon -- Route aggregation
+ *
+ *     (c) 2023--2023 Igor Putovny <igor.putovny@nic.cz>
+ *     (c) 2023       CZ.NIC, z.s.p.o.
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: Route aggregation
+ *
+ * This is an implementation of route aggregation functionality.
+ * It enables user to specify a set of route attributes in the configuarion file
+ * and then, for a given destination (net), aggregate routes with the same
+ * values of these attributes into a single multi-path route.
+ *
+ * Structure &channel contains pointer to aggregation list which is represented
+ * by &aggr_list_linearized. In rt_notify_aggregated(), attributes from this
+ * list are evaluated for every route of a given net and results are stored
+ * in &rte_val_list which contains pointer to this route and array of &f_val.
+ * Array of pointers to &rte_val_list entries is sorted using
+ * sort_rte_val_list(). For comparison of &f_val structures, val_compare()
+ * is used. Comparator function is written so that sorting is stable. If all
+ * attributes have the same values, routes are compared by their global IDs.
+ *
+ * After sorting, &rte_val_list entries containing equivalent routes will be
+ * adjacent to each other. Function process_rte_list() iterates through these
+ * entries to identify sequences of equivalent routes. New route will be
+ * created for each such sequence, even if only from a single route.
+ * Only attributes from the aggreagation list will be set for the new route.
+ * New &rta is created and prepare_rta() is used to copy static and dynamic
+ * attributes to new &rta from &rta of the original route. New route is created
+ * by create_merged_rte() from new &rta and exported to the routing table.
+ */
+
+#undef LOCAL_DEBUG
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "nest/bird.h"
+#include "nest/iface.h"
+#include "filter/filter.h"
+#include "aggregator.h"
+
+#include <stdlib.h>
+/*
+#include "nest/route.h"
+#include "nest/iface.h"
+#include "lib/resource.h"
+#include "lib/event.h"
+#include "lib/timer.h"
+#include "lib/string.h"
+#include "conf/conf.h"
+#include "filter/filter.h"
+#include "filter/data.h"
+#include "lib/hash.h"
+#include "lib/string.h"
+#include "lib/alloca.h"
+#include "lib/flowspec.h"
+*/
+
+/* Context of &f_val comparison. */
+struct cmp_ctx {
+  const struct aggregator_proto *p;
+  const struct network *net;
+  const int val_count;
+  u32 failed:1;
+};
+
+extern linpool *rte_update_pool;
+
+/*
+ * Set static attribute in @rta from static attribute in @old according to @sa.
+ */
+static void
+rta_set_static_attr(struct rta *rta, const struct rta *old, struct f_static_attr sa)
+{
+  switch (sa.sa_code)
+  {
+    case SA_NET:
+      break;
+
+    case SA_FROM:
+      rta->from = old->from;
+      break;
+
+    case SA_GW:
+      rta->dest = RTD_UNICAST;
+      rta->nh.gw = old->nh.gw;
+      rta->nh.iface = old->nh.iface;
+      rta->nh.next = NULL;
+      rta->hostentry = NULL;
+      rta->nh.labels = 0;
+      break;
+
+    case SA_SCOPE:
+      rta->scope = old->scope;
+      break;
+
+    case SA_DEST:
+      rta->dest = old->dest;
+      rta->nh.gw = IPA_NONE;
+      rta->nh.iface = NULL;
+      rta->nh.next = NULL;
+      rta->hostentry = NULL;
+      rta->nh.labels = 0;
+      break;
+
+    case SA_IFNAME:
+      rta->dest = RTD_UNICAST;
+      rta->nh.gw = IPA_NONE;
+      rta->nh.iface = old->nh.iface;
+      rta->nh.next = NULL;
+      rta->hostentry = NULL;
+      rta->nh.labels = 0;
+      break;
+
+    case SA_GW_MPLS:
+      rta->nh.labels = old->nh.labels;
+      memcpy(&rta->nh.label, &old->nh.label, sizeof(u32) * old->nh.labels);
+      break;
+
+    case SA_WEIGHT:
+      rta->nh.weight = old->nh.weight;
+      break;
+
+    case SA_PREF:
+      rta->pref = old->pref;
+      break;
+
+    default:
+      bug("Invalid static attribute access (%u/%u)", sa.f_type, sa.sa_code);
+  }
+}
+
+/*
+ * Compare list of &f_val entries.
+ * @count: number of &f_val entries
+ */
+static int
+same_val_list(const struct f_val *v1, const struct f_val *v2, uint len)
+{
+  for (uint i = 0; i < len; i++)
+    if (!val_same(&v1[i], &v2[i]))
+      return 0;
+
+  return 1;
+}
+
+/*
+ * Create and export new merged route.
+ * @old: first route in a sequence of equivalent routes that are to be merged
+ * @rte_val: first element in a sequence of equivalent rte_val_list entries
+ * @length: number of equivalent routes that are to be merged (at least 1)
+ * @ail: aggregation list
+ */
+static void
+aggregator_bucket_update(struct aggregator_proto *p, struct aggregator_bucket *bucket, struct network *net)
+{
+  /* Empty bucket */
+  if (!bucket->rte)
+  {
+    rte_update2(p->dst, net->n.addr, NULL, bucket->last_src);
+    bucket->last_src = NULL;
+    return;
+  }
+
+  /* Allocate RTA and EA list */
+  struct rta *rta = allocz(rta_size(bucket->rte->attrs));
+  rta->dest = RTD_UNREACHABLE;
+  rta->source = RTS_AGGREGATED;
+  rta->scope = SCOPE_UNIVERSE;
+
+  struct ea_list *eal = allocz(sizeof(struct ea_list) + sizeof(struct eattr) * p->aggr_on_da_count);
+  eal->next = NULL;
+  eal->count = 0;
+  rta->eattrs = eal;
+
+  /* Seed the attributes from aggregator rule */
+  for (uint i = 0; i < p->aggr_on_count; i++)
+  {
+    if (p->aggr_on[i].type == AGGR_ITEM_DYNAMIC_ATTR)
+    {
+      u32 ea_code = p->aggr_on[i].da.ea_code;
+      const struct eattr *e = ea_find(bucket->rte->attrs->eattrs, ea_code);
+
+      if (e)
+        eal->attrs[eal->count++] = *e;
+    }
+    else if (p->aggr_on[i].type == AGGR_ITEM_STATIC_ATTR)
+      rta_set_static_attr(rta, bucket->rte->attrs, p->aggr_on[i].sa);
+  }
+
+  struct rte *new = rte_get_temp(rta, bucket->rte->src);
+  new->net = net;
+
+  /*
+  log("=============== CREATE MERGED ROUTE ===============");
+  log("New route created: id = %d, protocol: %s", new->src->global_id, new->src->proto->name);
+  log("===================================================");
+  */
+
+  /* merge filter needs one argument called "routes" */
+  struct f_val val = {
+    .type = T_ROUTES_BLOCK,
+    .val.rte = bucket->rte,
+  };
+
+  /* Actually run the filter */
+  enum filter_return fret = f_eval_rte(p->merge_by, &new, rte_update_pool, 1, &val, 0);
+
+  /* Src must be stored now, rte_update2() may return new */
+  struct rte_src *new_src = new ? new->src : NULL;
+
+  /* Finally import the route */
+  switch (fret)
+  {
+    /* Pass the route to the protocol */
+    case F_ACCEPT:
+      rte_update2(p->dst, net->n.addr, new, bucket->last_src ?: new->src);
+      break;
+
+    /* Something bad happened */
+    default:
+      ASSERT_DIE(fret == F_ERROR);
+      /* fall through */
+
+    /* We actually don't want this route */
+    case F_REJECT:
+      if (bucket->last_src)
+       rte_update2(p->dst, net->n.addr, NULL, bucket->last_src);
+      break;
+  }
+
+  /* Switch source lock for bucket->last_src */
+  if (bucket->last_src != new_src)
+  {
+    if (new_src)
+      rt_lock_source(new_src);
+    if (bucket->last_src)
+      rt_unlock_source(bucket->last_src);
+
+    bucket->last_src = new_src;
+  }
+}
+
+/*
+ * Reload all the buckets on reconfiguration if merge filter has changed.
+ * TODO: make this splitted
+ */
+static void
+aggregator_reload_buckets(void *data)
+{
+  struct aggregator_proto *p = data;
+
+  HASH_WALK(p->buckets, next_hash, b)
+    if (b->rte)
+      aggregator_bucket_update(p, b, b->rte->net);
+  HASH_WALK_END;
+}
+
+
+/*
+ * Evaluate static attribute of @rt1 according to @sa
+ * and store result in @pos.
+ */
+static void
+eval_static_attr(const struct rte *rt1, struct f_static_attr sa, struct f_val *pos)
+{
+  const struct rta *rta = rt1->attrs;
+
+#define RESULT(_type, value, result)    \
+  do {                                  \
+    pos->type = _type;                  \
+    pos->val.value = result;            \
+  } while (0)
+
+  switch (sa.sa_code)
+  {
+    case SA_NET:       RESULT(sa.f_type, net, rt1->net->n.addr); break;
+    case SA_FROM:       RESULT(sa.f_type, ip, rta->from); break;
+    case SA_GW:                RESULT(sa.f_type, ip, rta->nh.gw); break;
+    case SA_PROTO:         RESULT(sa.f_type, s, rt1->src->proto->name); break;
+    case SA_SOURCE:        RESULT(sa.f_type, i, rta->source); break;
+    case SA_SCOPE:         RESULT(sa.f_type, i, rta->scope); break;
+    case SA_DEST:          RESULT(sa.f_type, i, rta->dest); break;
+    case SA_IFNAME:        RESULT(sa.f_type, s, rta->nh.iface ? rta->nh.iface->name : ""); break;
+    case SA_IFINDEX:   RESULT(sa.f_type, i, rta->nh.iface ? rta->nh.iface->index : 0); break;
+    case SA_WEIGHT:        RESULT(sa.f_type, i, rta->nh.weight + 1); break;
+    case SA_PREF:          RESULT(sa.f_type, i, rta->pref); break;
+    case SA_GW_MPLS:    RESULT(sa.f_type, i, rta->nh.labels ? rta->nh.label[0] : MPLS_NULL); break;
+    default:
+      bug("Invalid static attribute access (%u/%u)", sa.f_type, sa.sa_code);
+  }
+
+#undef RESULT
+}
+
+/*
+ * Evaluate dynamic attribute of @rt1 according to @da
+ * and store result in @pos.
+ */
+static void
+eval_dynamic_attr(const struct rte *rt1, struct f_dynamic_attr da, struct f_val *pos)
+{
+  const struct rta *rta = rt1->attrs;
+  const struct eattr *e = ea_find(rta->eattrs, da.ea_code);
+
+#define RESULT(_type, value, result)    \
+  do {                                  \
+    pos->type = _type;                  \
+    pos->val.value = result;            \
+  } while (0)
+
+#define RESULT_VOID         \
+  do {                      \
+    pos->type = T_VOID;     \
+  } while (0)
+
+  if (!e)
+  {
+    /* A special case: undefined as_path looks like empty as_path */
+    if (da.type == EAF_TYPE_AS_PATH)
+    {
+      RESULT(T_PATH, ad, &null_adata);
+      return;
+    }
+
+    /* The same special case for int_set */
+    if (da.type == EAF_TYPE_INT_SET)
+    {
+      RESULT(T_CLIST, ad, &null_adata);
+      return;
+    }
+
+    /* The same special case for ec_set */
+    if (da.type == EAF_TYPE_EC_SET)
+    {
+      RESULT(T_ECLIST, ad, &null_adata);
+      return;
+    }
+
+    /* The same special case for lc_set */
+    if (da.type == EAF_TYPE_LC_SET)
+    {
+      RESULT(T_LCLIST, ad, &null_adata);
+      return;
+    }
+
+    /* Undefined value */
+    RESULT_VOID;
+    return;
+  }
+
+  switch (e->type & EAF_TYPE_MASK)
+  {
+    case EAF_TYPE_INT:
+      RESULT(da.f_type, i, e->u.data);
+      break;
+    case EAF_TYPE_ROUTER_ID:
+      RESULT(T_QUAD, i, e->u.data);
+      break;
+    case EAF_TYPE_OPAQUE:
+      RESULT(T_ENUM_EMPTY, i, 0);
+      break;
+    case EAF_TYPE_IP_ADDRESS:
+      RESULT(T_IP, ip, *((ip_addr *) e->u.ptr->data));
+      break;
+    case EAF_TYPE_AS_PATH:
+      RESULT(T_PATH, ad, e->u.ptr);
+      break;
+    case EAF_TYPE_BITFIELD:
+      RESULT(T_BOOL, i, !!(e->u.data & (1u << da.bit)));
+      break;
+    case EAF_TYPE_INT_SET:
+      RESULT(T_CLIST, ad, e->u.ptr);
+      break;
+    case EAF_TYPE_EC_SET:
+      RESULT(T_ECLIST, ad, e->u.ptr);
+      break;
+    case EAF_TYPE_LC_SET:
+      RESULT(T_LCLIST, ad, e->u.ptr);
+      break;
+    default:
+      bug("Unknown dynamic attribute type");
+  }
+
+#undef RESULT
+#undef RESULT_VOID
+}
+
+static inline u32 aggr_route_hash(const rte *e)
+{
+  struct {
+    net *net;
+    struct rte_src *src;
+  } obj = {
+    .net = e->net,
+    .src = e->src,
+  };
+
+  return mem_hash(&obj, sizeof obj);
+}
+
+#define AGGR_RTE_KEY(n)                        (&(n)->rte)
+#define AGGR_RTE_NEXT(n)               ((n)->next_hash)
+#define AGGR_RTE_EQ(a,b)               (((a)->src == (b)->src) && ((a)->net == (b)->net))
+#define AGGR_RTE_FN(_n)                        aggr_route_hash(_n)
+#define AGGR_RTE_ORDER                 4 /* Initial */
+
+#define AGGR_RTE_REHASH                        aggr_rte_rehash
+#define AGGR_RTE_PARAMS                        /8, *2, 2, 2, 4, 24
+
+HASH_DEFINE_REHASH_FN(AGGR_RTE, struct aggregator_route);
+
+
+#define AGGR_BUCK_KEY(n)               (n)
+#define AGGR_BUCK_NEXT(n)              ((n)->next_hash)
+#define AGGR_BUCK_EQ(a,b)              (((a)->hash == (b)->hash) && (same_val_list((a)->aggr_data, (b)->aggr_data, p->aggr_on_count)))
+#define AGGR_BUCK_FN(n)                        ((n)->hash)
+#define AGGR_BUCK_ORDER                        4 /* Initial */
+
+#define AGGR_BUCK_REHASH               aggr_buck_rehash
+#define AGGR_BUCK_PARAMS               /8, *2, 2, 2, 4, 24
+
+HASH_DEFINE_REHASH_FN(AGGR_BUCK, struct aggregator_bucket);
+
+
+#define AGGR_DATA_MEMSIZE      (sizeof(struct f_val) * p->aggr_on_count)
+
+static void
+aggregator_rt_notify(struct proto *P, struct channel *src_ch, net *net, rte *new, rte *old)
+{
+  struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
+  ASSERT_DIE(src_ch == p->src);
+  struct aggregator_bucket *new_bucket = NULL, *old_bucket = NULL;
+  struct aggregator_route *old_route = NULL;
+
+  /* Find the objects for the old route */
+  if (old)
+    old_route = HASH_FIND(p->routes, AGGR_RTE, old);
+
+  if (old_route)
+    old_bucket = old_route->bucket;
+
+  /* Find the bucket for the new route */
+  if (new)
+  {
+    /* Routes are identical, do nothing */
+    if (old_route && rte_same(&old_route->rte, new))
+      return;
+
+    /* Evaluate route attributes. */
+    struct aggregator_bucket *tmp_bucket = sl_allocz(p->bucket_slab);
+
+    for (uint val_idx = 0; val_idx < p->aggr_on_count; val_idx++)
+    {
+      int type = p->aggr_on[val_idx].type;
+
+      switch (type)
+      {
+        case AGGR_ITEM_TERM: {
+          const struct f_line *line = p->aggr_on[val_idx].line;
+          struct rte *rt1 = new;
+          enum filter_return fret = f_eval_rte(line, &new, rte_update_pool, 0, NULL, &tmp_bucket->aggr_data[val_idx]);
+
+          if (rt1 != new)
+          {
+            rte_free(rt1);
+            log(L_WARN "Aggregator rule modifies the route, reverting");
+          }
+
+          if (fret > F_RETURN)
+            log(L_WARN "%s.%s: Wrong number of items left on stack after evaluation of aggregation list", rt1->src->proto->name, rt1->sender);
+
+          break;
+        }
+
+        case AGGR_ITEM_STATIC_ATTR: {
+          struct f_val *pos = &tmp_bucket->aggr_data[val_idx];
+          eval_static_attr(new, p->aggr_on[val_idx].sa, pos);
+          break;
+        }
+
+        case AGGR_ITEM_DYNAMIC_ATTR: {
+          struct f_val *pos = &tmp_bucket->aggr_data[val_idx];
+          eval_dynamic_attr(new, p->aggr_on[val_idx].da, pos);
+          break;
+        }
+
+        default:
+          break;
+      }
+    }
+
+    /* Compute the hash */
+    tmp_bucket->hash = mem_hash(tmp_bucket->aggr_data, AGGR_DATA_MEMSIZE);
+
+    /* Find the existing bucket */
+    if (new_bucket = HASH_FIND(p->buckets, AGGR_BUCK, tmp_bucket))
+      sl_free(tmp_bucket);
+    else
+    {
+      new_bucket = tmp_bucket;
+      HASH_INSERT2(p->buckets, AGGR_BUCK, p->p.pool, new_bucket);
+    }
+
+    /* Store the route attributes */
+    if (rta_is_cached(new->attrs))
+      rta_clone(new->attrs);
+    else
+      new->attrs = rta_lookup(new->attrs);
+
+    /* Insert the new route into the bucket */
+    struct aggregator_route *arte = sl_alloc(p->route_slab);
+    *arte = (struct aggregator_route) {
+      .bucket = new_bucket,
+      .rte = *new,
+    };
+    arte->rte.next = new_bucket->rte,
+    new_bucket->rte = &arte->rte;
+    new_bucket->count++;
+    HASH_INSERT2(p->routes, AGGR_RTE, p->p.pool, arte);
+  }
+
+  /* Remove the old route from its bucket */
+  if (old_bucket)
+  {
+    for (struct rte **k = &old_bucket->rte; *k; k = &(*k)->next)
+      if (*k == &old_route->rte)
+      {
+       *k = (*k)->next;
+       break;
+      }
+
+    old_bucket->count--;
+    HASH_REMOVE2(p->routes, AGGR_RTE, p->p.pool, old_route);
+    rta_free(old_route->rte.attrs);
+    sl_free(old_route);
+  }
+
+  /* Announce changes */
+  if (old_bucket)
+    aggregator_bucket_update(p, old_bucket, net);
+
+  if (new_bucket && (new_bucket != old_bucket))
+    aggregator_bucket_update(p, new_bucket, net);
+
+  /* Cleanup the old bucket if empty */
+  if (old_bucket && (!old_bucket->rte || !old_bucket->count))
+  {
+    ASSERT_DIE(!old_bucket->rte && !old_bucket->count);
+    HASH_REMOVE2(p->buckets, AGGR_BUCK, p->p.pool, old_bucket);
+    sl_free(old_bucket);
+  }
+}
+
+static int
+aggregator_preexport(struct channel *C, struct rte *new)
+{
+  struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, C->proto);
+  /* Reject our own routes */
+  if (new->sender == p->dst)
+    return -1;
+
+  /* Disallow aggregating already aggregated routes */
+  if (new->attrs->source == RTS_AGGREGATED)
+  {
+    log(L_ERR "Multiple aggregations of the same route not supported in BIRD 2.");
+    return -1;
+  }
+
+  return 0;
+}
+
+static void
+aggregator_postconfig(struct proto_config *CF)
+{
+  struct aggregator_config *cf = SKIP_BACK(struct aggregator_config, c, CF);
+
+  if (!cf->dst->table)
+    cf_error("Source table not specified");
+
+  if (!cf->src->table)
+    cf_error("Destination table not specified");
+
+  if (cf->dst->table->addr_type != cf->src->table->addr_type)
+    cf_error("Both tables must be of the same type");
+
+  cf->dst->in_filter = cf->src->in_filter;
+
+  cf->src->in_filter = FILTER_REJECT;
+  cf->dst->out_filter = FILTER_REJECT;
+
+  cf->dst->debug = cf->src->debug;
+}
+
+static struct proto *
+aggregator_init(struct proto_config *CF)
+{
+  struct proto *P = proto_new(CF);
+  struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
+  struct aggregator_config *cf = SKIP_BACK(struct aggregator_config, c, CF);
+
+  proto_configure_channel(P, &p->src, cf->src);
+  proto_configure_channel(P, &p->dst, cf->dst);
+
+  p->aggr_on_count = cf->aggr_on_count;
+  p->aggr_on_da_count = cf->aggr_on_da_count;
+  p->aggr_on = cf->aggr_on;
+  p->merge_by = cf->merge_by;
+
+  P->rt_notify = aggregator_rt_notify;
+  P->preexport = aggregator_preexport;
+
+  return P;
+}
+
+static int
+aggregator_start(struct proto *P)
+{
+  struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
+
+  p->bucket_slab = sl_new(P->pool, sizeof(struct aggregator_bucket) + AGGR_DATA_MEMSIZE);
+  HASH_INIT(p->buckets, P->pool, AGGR_BUCK_ORDER);
+
+  p->route_slab = sl_new(P->pool, sizeof(struct aggregator_route));
+  HASH_INIT(p->routes, P->pool, AGGR_RTE_ORDER);
+
+  p->reload_buckets = (event) {
+    .hook = aggregator_reload_buckets,
+    .data = p,
+  };
+
+  return PS_UP;
+}
+
+static int
+aggregator_shutdown(struct proto *P)
+{
+  struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
+
+  HASH_WALK_DELSAFE(p->buckets, next_hash, b)
+  {
+    while (b->rte)
+    {
+      struct aggregator_route *arte = SKIP_BACK(struct aggregator_route, rte, b->rte);
+      b->rte = arte->rte.next;
+      b->count--;
+      HASH_REMOVE(p->routes, AGGR_RTE, arte);
+      rta_free(arte->rte.attrs);
+      sl_free(arte);
+    }
+
+    ASSERT_DIE(b->count == 0);
+    HASH_REMOVE(p->buckets, AGGR_BUCK, b);
+    sl_free(b);
+  }
+  HASH_WALK_END;
+
+  return PS_DOWN;
+}
+
+static int
+aggregator_reconfigure(struct proto *P, struct proto_config *CF)
+{
+  struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
+  struct aggregator_config *cf = SKIP_BACK(struct aggregator_config, c, CF);
+
+  TRACE(D_EVENTS, "Reconfiguring");
+
+  /* Compare numeric values (shortcut) */
+  if (cf->aggr_on_count != p->aggr_on_count)
+    return 0;
+
+  if (cf->aggr_on_da_count != p->aggr_on_da_count)
+    return 0;
+
+  /* Compare aggregator rule */
+  for (uint i = 0; i < p->aggr_on_count; i++)
+    switch (cf->aggr_on[i].type)
+    {
+      case AGGR_ITEM_TERM:
+       if (!f_same(cf->aggr_on[i].line, p->aggr_on[i].line))
+         return 0;
+       break;
+      case AGGR_ITEM_STATIC_ATTR:
+       if (memcmp(&cf->aggr_on[i].sa, &p->aggr_on[i].sa, sizeof(struct f_static_attr)) != 0)
+         return 0;
+       break;
+      case AGGR_ITEM_DYNAMIC_ATTR:
+       if (memcmp(&cf->aggr_on[i].da, &p->aggr_on[i].da, sizeof(struct f_dynamic_attr)) != 0)
+         return 0;
+       break;
+      default:
+       bug("Broken aggregator rule");
+    }
+
+  /* Compare merge filter */
+  if (!f_same(cf->merge_by, p->merge_by))
+    ev_schedule(&p->reload_buckets);
+
+  p->aggr_on = cf->aggr_on;
+  p->merge_by = cf->merge_by;
+
+  return 1;
+}
+
+struct protocol proto_aggregator = {
+  .name =              "Aggregator",
+  .template =          "aggregator%d",
+  .class =             PROTOCOL_AGGREGATOR,
+  .preference =                1,
+  .channel_mask =      NB_ANY,
+  .proto_size =                sizeof(struct aggregator_proto),
+  .config_size =       sizeof(struct aggregator_config),
+  .postconfig =                aggregator_postconfig,
+  .init =              aggregator_init,
+  .start =             aggregator_start,
+  .shutdown =          aggregator_shutdown,
+  .reconfigure =       aggregator_reconfigure,
+};
+
+void
+aggregator_build(void)
+{
+  proto_build(&proto_aggregator);
+}
diff --git a/proto/aggregator/aggregator.h b/proto/aggregator/aggregator.h
new file mode 100644 (file)
index 0000000..19459b1
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ *     BIRD -- Aggregator Pseudoprotocol
+ *
+ *     (c) 2023       Igor Putovny <igor.putovny@nic.cz>
+ *     (c) 2023       Maria Matejka <mq@ucw.cz>
+ *     (c) 2023       CZ.NIC z.s.p.o.
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ *
+ *     This file contains the data structures used by Babel.
+ */
+
+#ifndef _BIRD_AGGREGATOR_H_
+#define _BIRD_AGGREGATOR_H_
+
+#include "nest/bird.h"
+#include "nest/protocol.h"
+#include "lib/hash.h"
+
+struct aggregator_config {
+  struct proto_config c;
+  struct channel_config *src, *dst;
+  uint aggr_on_count;
+  uint aggr_on_da_count;
+  struct aggr_item *aggr_on;
+  const struct f_line *merge_by;
+};
+
+struct aggregator_route {
+  struct aggregator_route *next_hash;
+  struct aggregator_bucket *bucket;
+  struct rte rte;
+};
+
+struct aggregator_bucket {
+  struct aggregator_bucket *next_hash;
+  struct rte *rte;                     /* Pointer to struct aggregator_route.rte */
+  struct rte_src *last_src;            /* Which src we announced the bucket last with */
+  u32 count;
+  u32 hash;
+  struct f_val aggr_data[0];
+};
+
+struct aggregator_proto {
+  struct proto p;
+  struct channel *src, *dst;
+
+  /* Buckets by aggregator rule */
+  HASH(struct aggregator_bucket) buckets;
+  slab *bucket_slab;
+
+  /* Routes by net and src */
+  HASH(struct aggregator_route) routes;
+  slab *route_slab;
+
+  /* Aggregator rule */
+  uint aggr_on_count;
+  uint aggr_on_da_count;
+  struct aggr_item *aggr_on;
+
+  /* Merge filter */
+  const struct f_line *merge_by;
+  event reload_buckets;
+};
+
+enum aggr_item_type {
+  AGGR_ITEM_TERM,
+  AGGR_ITEM_STATIC_ATTR,
+  AGGR_ITEM_DYNAMIC_ATTR,
+};
+
+struct aggr_item {
+  enum aggr_item_type type;
+  union {
+    struct f_static_attr sa;
+    struct f_dynamic_attr da;
+    const struct f_line *line;
+  };
+};
+
+struct aggr_item_node {
+  const struct aggr_item_node *next;
+  struct aggr_item i;
+};
+
+#endif
diff --git a/proto/aggregator/config.Y b/proto/aggregator/config.Y
new file mode 100644 (file)
index 0000000..44b7752
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ *     BIRD -- Aggregator configuration
+ *
+ *     (c) 2023       Igor Putovny <igor.putovny@nic.cz>
+ *     (c) 2023       Maria Matejka <mq@ucw.cz>
+ *     (c) 2023       CZ.NIC z.s.p.o.
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "proto/aggregator/aggregator.h"
+
+CF_DEFINES
+
+#define AGGREGATOR_CFG ((struct aggregator_config *) this_proto)
+#define AGGR_ITEM_ALLOC ((struct aggr_item_node *) cfg_allocz(sizeof(struct aggr_item_node)))
+
+
+CF_DECLS
+
+CF_KEYWORDS(AGGREGATOR, AGGREGATE, ON, MERGE, BY)
+
+%type <ai> aggr_item aggr_list
+
+CF_GRAMMAR
+
+proto: aggregator_proto ;
+
+aggregator_proto_start: proto_start AGGREGATOR
+{
+  this_proto = proto_config_new(&proto_aggregator, $1);
+  this_channel = AGGREGATOR_CFG->src = channel_config_new(NULL, "source", 0, this_proto);
+  AGGREGATOR_CFG->dst = channel_config_new(NULL, "destination", 0, this_proto);
+
+  AGGREGATOR_CFG->src->ra_mode = AGGREGATOR_CFG->dst->ra_mode = RA_ANY;
+};
+
+aggregator_proto_item:
+   proto_item
+ | channel_item_
+ | PEER TABLE rtable { AGGREGATOR_CFG->dst->table = $3; }
+ | AGGREGATE ON aggr_list {
+    if (AGGREGATOR_CFG->aggr_on)
+      cf_error("Only one aggregate on clause allowed");
+
+    _Bool net_present = 0;
+    int count = 0;
+
+    for (const struct aggr_item_node *item = $3; item; item = item->next) {
+//      log(L_WARN "type %d sacode %d", item->i.type, item->i.sa.sa_code);
+      if (item->i.type == AGGR_ITEM_STATIC_ATTR && item->i.sa.sa_code == SA_NET)
+       net_present = 1;
+
+      count++;
+    }
+
+   if (!net_present)
+     cf_error("'NET' must be present");
+
+   AGGREGATOR_CFG->aggr_on = cfg_alloc(sizeof(struct aggr_item) * count);
+
+   int pos = 0;
+   for (const struct aggr_item_node *item = $3; item; item = item->next) {
+     if (item->i.type == AGGR_ITEM_DYNAMIC_ATTR)
+       AGGREGATOR_CFG->aggr_on_da_count++;
+
+     AGGREGATOR_CFG->aggr_on[pos++] = item->i;
+   }
+
+   AGGREGATOR_CFG->aggr_on_count = pos;
+ }
+ | MERGE BY {
+   cf_push_block_scope(new_config);
+   cf_create_symbol(new_config, "routes", SYM_VARIABLE | T_ROUTES_BLOCK, offset, f_new_var(sym_->scope));
+ } function_body {
+   cf_pop_block_scope(new_config);
+   $4->args++;
+   AGGREGATOR_CFG->merge_by = $4;
+ }
+;
+
+aggregator_proto_opts: /* empty */ | aggregator_proto_opts aggregator_proto_item ';' ;
+aggregator_proto: aggregator_proto_start proto_name '{' aggregator_proto_opts '}' ;
+
+
+aggr_list:
+   aggr_item
+ | aggr_list ',' aggr_item {
+       if ($3 == NULL) {
+         $$ = $1;
+       } else {
+         $$ = $3;
+         $$->next = $1;
+       }
+   }
+ ;
+
+aggr_item:
+   '(' term ')' {
+       $$ = AGGR_ITEM_ALLOC;
+       $$->i.type = AGGR_ITEM_TERM;
+       $$->i.line = f_linearize($2, 1);
+    }
+  | CF_SYM_KNOWN {
+      switch ($1->class) {
+        case SYM_ATTRIBUTE:
+          $$ = AGGR_ITEM_ALLOC;
+          $$->i.type = AGGR_ITEM_DYNAMIC_ATTR;
+          $$->i.da = *$1->attribute;
+          break;
+        case SYM_CONSTANT_RANGE:
+          $$ = NULL;
+          break;
+        default:
+          cf_error("Can't aggregate on symbol type %s.", cf_symbol_class_name($1));
+      }
+    }
+  | dynamic_attr {
+      $$ = AGGR_ITEM_ALLOC;
+      $$->i.type = AGGR_ITEM_DYNAMIC_ATTR;
+      $$->i.da = $1;
+    }
+  | static_attr {
+      $$ = AGGR_ITEM_ALLOC;
+      $$->i.type = AGGR_ITEM_STATIC_ATTR;
+      $$->i.sa = $1;
+    }
+  ;
+
+CF_CODE
+
+CF_END
diff --git a/proto/aggregator/test.conf b/proto/aggregator/test.conf
new file mode 100644 (file)
index 0000000..e5e1e26
--- /dev/null
@@ -0,0 +1,116 @@
+log "bird.log" all;
+
+protocol device {}
+
+protocol static {
+  ipv6;
+  route 2001:db8:0::/48 unreachable { bgp_path.prepend(65432); bgp_path.prepend(4200000000); };
+  route 2001:db8:1::/48 unreachable;
+  route 2001:db8:2::/48 unreachable;
+  route 2001:db8:3::/48 unreachable;
+  route 2001:db8:4::/48 unreachable;
+  route 2001:db8:5::/48 unreachable;
+  route 2001:db8:6::/48 unreachable;
+  route 2001:db8:7::/48 unreachable;
+  route 2001:db8:8::/48 unreachable;
+  route 2001:db8:9::/48 unreachable;
+  route 2001:db8:a::/48 unreachable;
+  route 2001:db8:b::/48 unreachable;
+  route 2001:db8:c::/48 unreachable;
+  route 2001:db8:d::/48 unreachable;
+  route 2001:db8:e::/48 unreachable;
+  route 2001:db8:f::/48 unreachable;
+}
+
+protocol static {
+  ipv6 {
+    import filter {
+      bgp_med = 1;
+      bgp_community = -empty-.add((65533,1)).add((65500,0xe));
+      accept;
+    };
+  };
+  route 2001:db8:1::/48 unreachable;
+  route 2001:db8:3::/48 unreachable;
+  route 2001:db8:5::/48 unreachable;
+  route 2001:db8:7::/48 unreachable;
+  route 2001:db8:9::/48 unreachable;
+  route 2001:db8:b::/48 unreachable;
+  route 2001:db8:d::/48 unreachable;
+  route 2001:db8:f::/48 unreachable;
+}
+
+protocol static {
+  ipv6 {
+    import filter {
+      bgp_med = 2;
+      bgp_community = -empty-.add((65533,2)).add((65500,0xd));
+      accept;
+    };
+  };
+  route 2001:db8:2::/48 unreachable;
+  route 2001:db8:3::/48 unreachable;
+  route 2001:db8:6::/48 unreachable;
+  route 2001:db8:7::/48 unreachable;
+  route 2001:db8:a::/48 unreachable;
+  route 2001:db8:b::/48 unreachable;
+  route 2001:db8:e::/48 unreachable;
+  route 2001:db8:f::/48 unreachable;
+}
+
+protocol static {
+  ipv6 {
+    import filter {
+      bgp_med = 4;
+      bgp_community = -empty-.add((65533,4)).add((65500,0xb));
+      accept;
+    };
+  };
+  route 2001:db8:4::/48 unreachable;
+  route 2001:db8:5::/48 unreachable;
+  route 2001:db8:6::/48 unreachable;
+  route 2001:db8:7::/48 unreachable;
+  route 2001:db8:c::/48 unreachable;
+  route 2001:db8:d::/48 unreachable;
+  route 2001:db8:e::/48 unreachable;
+  route 2001:db8:f::/48 unreachable;
+}
+
+protocol static {
+  ipv6 {
+    import filter {
+      bgp_med = 8;
+      bgp_community = -empty-.add((65533,8)).add((65500,0x7));
+      accept;
+    };
+  };
+  route 2001:db8:8::/48 unreachable;
+  route 2001:db8:9::/48 unreachable;
+  route 2001:db8:a::/48 unreachable;
+  route 2001:db8:b::/48 unreachable;
+  route 2001:db8:c::/48 unreachable;
+  route 2001:db8:d::/48 unreachable;
+  route 2001:db8:e::/48 unreachable;
+  route 2001:db8:f::/48 unreachable;
+}
+
+ipv6 table agr_result;
+
+protocol aggregator {
+  table master6;
+  peer table agr_result;
+  export all;
+  aggregate on net,(defined(bgp_med));
+  merge by {
+    print "Merging all these: ", routes;
+    bgp_med = 0;
+    for route r in routes do {
+      if ! defined(r.bgp_med) then { unset(bgp_med); accept; }
+
+      print r, " bgp_med: ", r.bgp_med;
+      bgp_med = bgp_med + r.bgp_med;
+      bgp_community = bgp_community.add(r.bgp_community);
+    }
+    accept;
+  };
+}
index bb93305ed2fcc89d2d5ac8be316522be28b9d516..cf7a476843aca4d409863e54a42206e9f3a8405d 100644 (file)
@@ -113,7 +113,7 @@ static_announce_rte(struct static_proto *p, struct static_route *r)
     net_copy(e->net->n.addr, r->net);
 
     /* Evaluate the filter */
-    f_eval_rte(r->cmds, &e, static_lp);
+    f_eval_rte(r->cmds, &e, static_lp, 0, NULL, NULL);
 
     /* Remove the temporary node */
     e->net = NULL;