diff options
-rw-r--r-- | conf/conf.h | 3 | ||||
-rw-r--r-- | conf/confbase.Y | 1 | ||||
-rw-r--r-- | configure.ac | 3 | ||||
-rw-r--r-- | filter/config.Y | 51 | ||||
-rw-r--r-- | filter/data.c | 45 | ||||
-rw-r--r-- | filter/data.h | 5 | ||||
-rw-r--r-- | filter/f-inst.c | 32 | ||||
-rw-r--r-- | filter/filter.c | 33 | ||||
-rw-r--r-- | filter/filter.h | 3 | ||||
-rw-r--r-- | nest/a-path.c | 23 | ||||
-rw-r--r-- | nest/attrs.h | 2 | ||||
-rw-r--r-- | nest/proto.c | 1 | ||||
-rw-r--r-- | nest/protocol.h | 3 | ||||
-rw-r--r-- | nest/route.h | 5 | ||||
-rw-r--r-- | nest/rt-attr.c | 5 | ||||
-rw-r--r-- | nest/rt-table.c | 5 | ||||
-rw-r--r-- | proto/aggregator/Doc | 1 | ||||
-rw-r--r-- | proto/aggregator/Makefile | 6 | ||||
-rw-r--r-- | proto/aggregator/aggregator.c | 731 | ||||
-rw-r--r-- | proto/aggregator/aggregator.h | 86 | ||||
-rw-r--r-- | proto/aggregator/config.Y | 134 | ||||
-rw-r--r-- | proto/aggregator/test.conf | 116 | ||||
-rw-r--r-- | proto/static/static.c | 2 |
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 c9c52038..a7cf0a5d 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" +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]) diff --git a/filter/config.Y b/filter/config.Y index dfabddf7..a15683f5 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, @@ -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 diff --git a/filter/data.c b/filter/data.c index 89b75e56..e268a8ec 100644 --- a/filter/data.c +++ b/filter/data.c @@ -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); } @@ -570,6 +583,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 @@ -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; } } diff --git a/filter/data.h b/filter/data.h index 3430455a..0a521ec5 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 @@ -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; diff --git a/filter/f-inst.c b/filter/f-inst.c index a7bec81e..4356a735 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 65fb92a4..560778a8 100644 --- a/filter/filter.c +++ b/filter/filter.c @@ -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; } 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 fcd5ac16..aee3468b 100644 --- a/nest/attrs.h +++ b/nest/attrs.h @@ -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 */ 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 dad0b781..596e810e 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, @@ -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; /* diff --git a/nest/route.h b/nest/route.h index 7aec7117..feb1fa60 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; |