summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikael Magnusson <mikma@users.sourceforge.net>2023-11-25 23:36:44 +0100
committerMikael Magnusson <mikma@users.sourceforge.net>2023-11-25 23:36:44 +0100
commit9a3172260987d2fd2b70a90071128d0201cc2e8f (patch)
tree92774b42cf6b89468f7a1b2663d8587beac8653f
parentf9c422250bd32978c104ffdc2a801be75dbf9068 (diff)
parent977b82fba49b22d9548546d88b105945921efaed (diff)
Merge commit '977b82fb' into wireguard-next-tmp7-1
-rw-r--r--conf/conf.h3
-rw-r--r--conf/confbase.Y1
-rw-r--r--configure.ac3
-rw-r--r--filter/config.Y51
-rw-r--r--filter/data.c45
-rw-r--r--filter/data.h5
-rw-r--r--filter/f-inst.c32
-rw-r--r--filter/filter.c33
-rw-r--r--filter/filter.h3
-rw-r--r--nest/a-path.c23
-rw-r--r--nest/attrs.h2
-rw-r--r--nest/proto.c1
-rw-r--r--nest/protocol.h3
-rw-r--r--nest/route.h5
-rw-r--r--nest/rt-attr.c5
-rw-r--r--nest/rt-table.c5
-rw-r--r--proto/aggregator/Doc1
-rw-r--r--proto/aggregator/Makefile6
-rw-r--r--proto/aggregator/aggregator.c731
-rw-r--r--proto/aggregator/aggregator.h86
-rw-r--r--proto/aggregator/config.Y134
-rw-r--r--proto/aggregator/test.conf116
-rw-r--r--proto/static/static.c2
23 files changed, 1257 insertions, 39 deletions
diff --git a/conf/conf.h b/conf/conf.h
index 486499ad..b07b417c 100644
--- a/conf/conf.h
+++ b/conf/conf.h
@@ -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 *);
diff --git a/conf/confbase.Y b/conf/confbase.Y
index 0364bc6e..69a7676c 100644
--- a/conf/confbase.Y
+++ b/conf/confbase.Y
@@ -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
diff --git a/configure.ac b/configure.ac
index de3cb50a..cbdf2ca3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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 wireguard"
+all_protocols="aggregator $proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static wireguard"
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])
diff --git a/filter/config.Y b/filter/config.Y
index 5abaca52..ed1970b0 100644
--- a/filter/config.Y
+++ b/filter/config.Y
@@ -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,
@@ -376,6 +376,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
@@ -435,8 +454,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");
}
}
@@ -579,6 +598,7 @@ type:
| CLIST { $$ = T_CLIST; }
| ECLIST { $$ = T_ECLIST; }
| LCLIST { $$ = T_LCLIST; }
+ | ROUTE { $$ = T_ROUTE; }
| SUBTLV { $$ = T_SUBTLV; }
| TLVLIST { $$ = T_TLVLIST; }
| type SET {
@@ -974,7 +994,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);
@@ -1008,6 +1028,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:
@@ -1033,9 +1063,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
@@ -1188,16 +1218,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
diff --git a/filter/data.c b/filter/data.c
index d01b5764..d2347684 100644
--- a/filter/data.c
+++ b/filter/data.c
@@ -60,6 +60,9 @@ static const char * const f_type_str[] = {
[T_SUBTLV] = "subtlv",
[T_TLVLIST] = "tlvlist",
[T_RD] = "rd",
+
+ [T_ROUTE] = "route",
+ [T_ROUTES_BLOCK] = "block of routes",
};
const char *
@@ -82,6 +85,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;
};
}
@@ -210,6 +214,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;
}
@@ -300,6 +309,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;
case T_TLVLIST:
return tlvlist_same(v1->val.tl, v2->val.tl);
default:
@@ -605,6 +618,36 @@ val_in_range(const struct f_val *v1, const struct f_val *v2)
}
/*
+ * 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
*/
void
@@ -634,6 +677,8 @@ val_format(const struct f_val *v, buffer *buf)
case T_LCLIST: lc_set_format(v->val.ad, -1, buf2, 1000); buffer_print(buf, "(lclist %s)", buf2); return;
case T_TLVLIST: tlv_set_format(v->val.tl, -1, buf2, 1000); buffer_print(buf, "(tlvlist %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;
}
}
diff --git a/filter/data.h b/filter/data.h
index 73242f94..811678f2 100644
--- a/filter/data.h
+++ b/filter/data.h
@@ -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
@@ -65,6 +66,8 @@ enum f_type {
T_SUBTLV = 0x2d, /* Tunnel encapsulation sub-TLV */
T_TLVLIST = 0x2e, /* Tunnel encapsulation TLV list */
+ T_ROUTE = 0x78,
+ T_ROUTES_BLOCK = 0x79,
T_SET = 0x80,
T_PREFIX_SET = 0x81,
} PACKED;
@@ -93,6 +96,7 @@ struct f_val {
const struct adata *ad;
const struct f_path_mask *path_mask;
struct f_path_mask_item pmi;
+ struct rte *rte;
struct te_subtlv st;
const struct te_tlv *tlv;
const struct te_tlvlist *tl;
@@ -142,6 +146,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;
diff --git a/filter/f-inst.c b/filter/f-inst.c
index 59f3415d..739cbf5d 100644
--- a/filter/f-inst.c
+++ b/filter/f-inst.c
@@ -617,6 +617,22 @@
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)
@@ -654,11 +670,13 @@
}
}
- 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)
{
@@ -797,13 +815,15 @@
}
}
- 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));
diff --git a/filter/filter.c b/filter/filter.c
index e2338be8..7dad5dbb 100644
--- a/filter/filter.c
+++ b/filter/filter.c
@@ -92,6 +92,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;
};
@@ -159,18 +162,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;
@@ -239,7 +244,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
@@ -273,6 +277,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 );
@@ -287,7 +297,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) {
/*
@@ -339,7 +349,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,
@@ -349,10 +359,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);
}
/*
@@ -371,7 +378,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;
}
diff --git a/filter/filter.h b/filter/filter.h
index 91de696c..18ff0874 100644
--- a/filter/filter.h
+++ b/filter/filter.h
@@ -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);
diff --git a/nest/a-path.c b/nest/a-path.c
index c421b41f..aba2c86d 100644
--- a/nest/a-path.c
+++ b/nest/a-path.c
@@ -670,6 +670,29 @@ as_path_filter(struct linpool *pool, const struct adata *path, const struct f_va
}
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)
{
if (!path)
diff --git a/nest/attrs.h b/nest/attrs.h
index a874ffe1..99174b2b 100644
--- a/nest/attrs.h
+++ b/nest/attrs.h
@@ -57,6 +57,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)
@@ -244,6 +245,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 */
diff --git a/nest/proto.c b/nest/proto.c
index bc7b7cc8..48ffade5 100644
--- a/nest/proto.c
+++ b/nest/proto.c
@@ -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)
{
diff --git a/nest/protocol.h b/nest/protocol.h
index 4b06d54c..7d41dd13 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -39,6 +39,7 @@ struct symbol;
enum protocol_class {
PROTOCOL_NONE,
+ PROTOCOL_AGGREGATOR,
PROTOCOL_BABEL,
PROTOCOL_BFD,
PROTOCOL_BGP,
@@ -104,7 +105,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,
proto_wireguard;
diff --git a/nest/route.h b/nest/route.h
index 4b5ddcdf..c515420c 100644
--- a/nest/route.h
+++ b/nest/route.h
@@ -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 */
diff --git a/nest/rt-attr.c b/nest/rt-attr.c
index d793c72e..b341ff46 100644
--- a/nest/rt-attr.c
+++ b/nest/rt-attr.c
@@ -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",
diff --git a/nest/rt-table.c b/nest/rt-table.c
index 742e2f05..8b41ffee 100644
--- a/nest/rt-table.c
+++ b/nest/rt-table.c
@@ -117,7 +117,7 @@
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
index 00000000..6111f2f1
--- /dev/null
+++ b/proto/aggregator/Doc
@@ -0,0 +1 @@
+S aggregator.c
diff --git a/proto/aggregator/Makefile b/proto/aggregator/Makefile
new file mode 100644
index 00000000..d1dae8dd
--- /dev/null
+++ b/proto/aggregator/Makefile
@@ -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
index 00000000..9d95c7db
--- /dev/null
+++ b/proto/aggregator/aggregator.c
@@ -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
index 00000000..19459b1d
--- /dev/null
+++ b/proto/aggregator/aggregator.h
@@ -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
index 00000000..44b7752f
--- /dev/null
+++ b/proto/aggregator/config.Y
@@ -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
index 00000000..e5e1e267
--- /dev/null
+++ b/proto/aggregator/test.conf
@@ -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;
+ };
+}
diff --git a/proto/static/static.c b/proto/static/static.c
index bb93305e..cf7a4768 100644
--- a/proto/static/static.c
+++ b/proto/static/static.c
@@ -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;