diff --git a/Makefile.in b/Makefile.in index 839efe24..e89ae523 100644 --- a/Makefile.in +++ b/Makefile.in @@ -23,6 +23,9 @@ INSTALL=@INSTALL@ INSTALL_PROGRAM=@INSTALL_PROGRAM@ INSTALL_DATA=@INSTALL_DATA@ +STAYRTR_BINARY=@STAYRTR_BINARY@ +NETLAB_DATA_SUFFIX=v2 + client=$(addprefix $(exedir)/,@CLIENT@) daemon=$(exedir)/bird protocols=@protocols@ @@ -186,6 +189,16 @@ check: tests tests_run tests: $(tests_targets) tests_run: $(tests_targets_ok) +aux-test-prepare: all + $(E)echo Preparing netlab test suite ... + $(Q)cd $(srcdir) && git submodule update --init --force --checkout aux-tools + $(Q)cd $(srcdir)/aux-tools && git clean -fxdq || true + $(Q)cd $(srcdir)/aux-tools/netlab/common && ln $(shell readlink -f $(exedir)/bird) && ln $(shell readlink -f $(exedir)/birdc) + $(Q)cd $(srcdir)/aux-tools/netlab/common && ln -s $(STAYRTR_BINARY) stayrtr + $(E)echo Netlab test suite prepared. + +-include $(srcdir)/aux-tools/netlab/tests-$(NETLAB_DATA_SUFFIX).mk + STATIC_CHECKERS_ENABLE := nullability.NullableDereferenced nullability.NullablePassedToNonnull nullability.NullableReturnedFromNonnull optin.portability.UnixAPI valist.CopyToSelf valist.Uninitialized valist.Unterminated STATIC_CHECKERS_DISABLE := deadcode.DeadStores STATIC_SCAN_FLAGS := -o $(objdir)/static-scan/ $(addprefix -enable-checker ,$(STATIC_CHECKERS_ENABLE)) $(addprefix -disable-checker ,$(STATIC_CHECKERS_DISABLE)) diff --git a/conf/confbase.Y b/conf/confbase.Y index 8127bfcb..60f5679c 100644 --- a/conf/confbase.Y +++ b/conf/confbase.Y @@ -95,6 +95,8 @@ CF_DECLS struct timeformat *tf; mpls_label_stack *mls; struct bytestring *bs; + struct aggr_item *ai; + struct aggr_item_linearized *ail; } %token END CLI_MARKER INVALID_TOKEN ELSECOL DDOT diff --git a/configure.ac b/configure.ac index c9c52038..e38004c9 100644 --- a/configure.ac +++ b/configure.ac @@ -80,10 +80,18 @@ AC_ARG_WITH([iproutedir], [given_iproutedir="yes"] ) +AC_ARG_WITH([stayrtr], + [AS_HELP_STRING([--with-stayrtr=PATH], [path to stayrtr built binary for RPKI testing @<:@/usr/bin/stayrtr@:>@])], + [], + [with_stayrtr="/usr/bin/stayrtr"] +) + AC_ARG_VAR([FLEX], [location of the Flex program]) AC_ARG_VAR([BISON], [location of the Bison program]) AC_ARG_VAR([M4], [location of the M4 program]) +AC_SUBST([STAYRTR_BINARY], [${with_stayrtr}]) + if test "$enable_debug_expensive" = yes; then enable_debug=yes fi diff --git a/filter/config.Y b/filter/config.Y index 9dd59471..4229f937 100644 --- a/filter/config.Y +++ b/filter/config.Y @@ -321,8 +321,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"); } } @@ -356,6 +356,7 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN, ACCEPT, REJECT, ERROR, INT, BOOL, IP, PREFIX, RD, PAIR, QUAD, EC, LC, SET, STRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST, + ROUTES, IF, THEN, ELSE, CASE, FOR, IN, DO, TRUE, FALSE, RT, RO, UNKNOWN, GENERIC, @@ -462,6 +463,7 @@ type: | CLIST { $$ = T_CLIST; } | ECLIST { $$ = T_ECLIST; } | LCLIST { $$ = T_LCLIST; } + | ROUTE { $$ = T_ROUTE; } | type SET { switch ($1) { case T_INT: @@ -839,7 +841,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); @@ -900,9 +902,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 @@ -947,6 +949,16 @@ term: | FORMAT '(' term ')' { $$ = f_new_inst(FI_FORMAT, $3); } | function_call + + | ROUTES { $$ = f_new_inst(FI_ROUTES_GET); } + | CF_SYM_KNOWN '.' static_attr { + cf_assert_symbol($1, SYM_VARIABLE); + $$ = f_new_inst(FI_RTA_GET, f_new_inst(FI_VAR_GET, $1), $3); + } + | CF_SYM_KNOWN '.' dynamic_attr { + cf_assert_symbol($1, SYM_VARIABLE); + $$ = f_new_inst(FI_EA_GET, f_new_inst(FI_VAR_GET, $1), $3); + } ; break_command: diff --git a/filter/data.c b/filter/data.c index 80098b64..c2731a38 100644 --- a/filter/data.c +++ b/filter/data.c @@ -54,6 +54,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 * @@ -76,6 +79,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; }; } @@ -204,6 +208,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; } @@ -286,6 +295,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); } @@ -559,6 +572,36 @@ val_in_range(const struct f_val *v1, const struct f_val *v2) return F_CMP_ERROR; } +/* + * rte_format - format route information + */ +static void +rte_format(const struct rte *rte, buffer *buf) +{ + if (rte) + buffer_print(buf, "Route [%d] to %N from %s.%s via %s", + rte->src->global_id, rte->net->n.addr, + rte->sender->proto->name, rte->sender->name, + rte->src->proto->name); + else + buffer_puts(buf, "[No route]"); +} + +static void +rte_block_format(const struct adata *ad, buffer *buf) +{ + struct rte **routes = ((struct rte_block *)ad)->routes; + const int count = (ad->length - sizeof(struct rte_block) + sizeof(struct adata)) / sizeof(struct rte *); + + buffer_print(buf, "Block of %d routes:"); + + for (int i = 0; i < count; i++) + { + buffer_print(buf, "\t%d: ", i); + rte_format(routes[i], buf); + } +} + /* * val_format - format filter value */ @@ -587,6 +630,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.ad, buf); return; default: buffer_print(buf, "[unknown type %x]", v->type); return; } } diff --git a/filter/data.h b/filter/data.h index ab7748ce..abb8763b 100644 --- a/filter/data.h +++ b/filter/data.h @@ -59,6 +59,8 @@ enum f_type { T_RD = 0x2a, /* Route distinguisher for VPN addresses */ T_PATH_MASK_ITEM = 0x2b, /* Path mask item for path mask constructors */ + T_ROUTE = 0x78, + T_ROUTES_BLOCK = 0x79, T_SET = 0x80, T_PREFIX_SET = 0x81, } PACKED; @@ -69,6 +71,38 @@ struct f_method { uint arg_num; }; +typedef struct adata { + uint length; /* Length of data */ + byte data[0]; +} adata; + +extern const adata null_adata; /* adata of length 0 */ + +/* Large community value */ +typedef struct lcomm { + u32 asn; + u32 ldp1; + u32 ldp2; +} lcomm; + +struct f_path_mask_item { + union { + u32 asn; /* PM_ASN */ + const struct f_line *expr; /* PM_ASN_EXPR */ + const struct f_tree *set; /* PM_ASN_SET */ + struct { /* PM_ASN_RANGE */ + u32 from; + u32 to; + }; + }; + int kind; +}; + +struct f_path_mask { + uint len; + struct f_path_mask_item item[0]; +}; + /* Filter value; size of this affects filter memory consumption */ struct f_val { enum f_type type; /* T_* */ @@ -84,6 +118,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; }; @@ -129,6 +164,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; @@ -202,6 +238,11 @@ struct f_trie_walk_state const struct f_trie_node *stack[TRIE_STACK_LENGTH]; }; +struct rte_block { + struct adata ad; + struct rte *routes[]; +}; + struct f_tree *f_new_tree(void); struct f_tree *build_tree(struct f_tree *); const struct f_tree *find_tree(const struct f_tree *t, const struct f_val *val); diff --git a/filter/f-inst.c b/filter/f-inst.c index 9d71b93a..12ec60f5 100644 --- a/filter/f-inst.c +++ b/filter/f-inst.c @@ -541,6 +541,12 @@ RESULT_VAL(val); } + INST(FI_ROUTES_GET, 0, 1) { + NEVER_CONSTANT; + FID_INTERPRET_BODY() + RESULT(T_ROUTES_BLOCK, ad, fs->val->val.ad); + } + METHOD_R(T_PATH, empty, 0, T_PATH, ad, &null_adata); METHOD_R(T_CLIST, empty, 0, T_CLIST, ad, &null_adata); METHOD_R(T_ECLIST, empty, 0, T_ECLIST, ad, &null_adata); @@ -605,6 +611,15 @@ METHOD_CONSTRUCTOR("!for_next"); } + INST(FI_ROUTES_BLOCK_FOR_NEXT, 3, 0) { + NEVER_CONSTANT; + ARG(1, T_ROUTE); + if (rte_set_walk(v1.val.ad, &v2.val.i, &v3.val.rte)) + LINE(2,0); + + METHOD_CONSTRUCTOR("!for_next"); + } + INST(FI_CONDITION, 1, 0) { ARG(1, T_BOOL); if (v1.val.i) @@ -642,11 +657,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) { @@ -785,13 +802,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) { /* A special case: undefined as_path looks like empty as_path */ diff --git a/filter/filter.c b/filter/filter.c index 74fc904e..f98eec97 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; }; @@ -237,6 +240,29 @@ interpret(struct filter_state *fs, const struct f_line *line, struct f_val *val) return F_ERROR; } +/** + * Evaluation for attributes comparison + */ +enum filter_return +f_aggr_eval_line(const struct f_line *line, struct rte **rte, struct linpool *pool, struct f_val *pres) +{ + /* Initialize the filter state */ + filter_state = (struct filter_state) { + .stack = &filter_stack, + .rte = rte, + .pool = pool, + }; + + LOG_BUFFER_INIT(filter_state.buf); + + /* Run the interpreter itself */ + enum filter_return fret = interpret(&filter_state, line, pres); + + if (filter_state.old_rta) + log("Warning: route changed during filter evaluation"); + + return fret; +} /** * f_run - run a filter for a route @@ -271,6 +297,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_val(filter, rte, tmp_pool, NULL, flags); +} + +enum filter_return +f_run_val(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, const struct f_val *val, int flags) +{ int rte_cow = ((*rte)->flags & REF_COW); DBG( "Running filter `%s'...", filter->name ); @@ -279,6 +311,7 @@ f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, i .stack = &filter_stack, .rte = rte, .pool = tmp_pool, + .val = val, .flags = flags, }; diff --git a/filter/filter.h b/filter/filter.h index 26c1037b..f5035805 100644 --- a/filter/filter.h +++ b/filter/filter.h @@ -51,7 +51,9 @@ struct filter { struct rte; +enum filter_return f_aggr_eval_line(const struct f_line *line, struct rte **rte, struct linpool *pool, struct f_val *pres); enum filter_return f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, int flags); +enum filter_return f_run_val(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, const struct f_val *val, int flags); enum filter_return f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool); uint f_eval_int(const struct f_line *expr); enum filter_return f_eval_buf(const struct f_line *expr, struct linpool *tmp_pool, buffer *buf); diff --git a/nest/Makefile b/nest/Makefile index 5a244c75..b7638f77 100644 --- a/nest/Makefile +++ b/nest/Makefile @@ -1,4 +1,4 @@ -src := a-path.c a-set.c cli.c cmds.c iface.c locks.c neighbor.c password.c proto.c proto-build.c rt-attr.c rt-dev.c rt-fib.c rt-show.c rt-table.c +src := a-path.c a-set.c cli.c cmds.c iface.c locks.c neighbor.c password.c proto.c proto-build.c rt-attr.c rt-dev.c rt-fib.c rt-show.c rt-table.c aggregator.c obj := $(src-o-files) $(all-daemon) $(cf-local) 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 @@ -669,6 +669,29 @@ as_path_filter(struct linpool *pool, const struct adata *path, const struct f_va return res; } +int +as_path_compare(const struct adata *path1, const struct adata *path2) +{ + uint pos1 = 0; + uint pos2 = 0; + uint val1 = 0; + uint val2 = 0; + + while (1) + { + int res1 = as_path_walk(path1, &pos1, &val1); + int res2 = as_path_walk(path2, &pos2, &val2); + + if (res1 == 0 && res2 == 0) + return 0; + + if (val1 == val2) + continue; + + return val1 < val2 ? -1 : 1; + } +} + int as_path_walk(const struct adata *path, uint *pos, uint *val) { diff --git a/nest/a-set.c b/nest/a-set.c index 40ed573c..90a9e03d 100644 --- a/nest/a-set.c +++ b/nest/a-set.c @@ -741,3 +741,19 @@ lc_set_walk(const struct adata *list, uint *pos, lcomm *val) return 1; } + +int +rte_set_walk(const struct adata *list, u32 *pos, struct rte **val) +{ + if (!list) + return 0; + + if (*pos >= (u32)rte_set_get_size(list)) + return 0; + + struct rte *res = rte_set_get_data(list, *pos); + *val = res; + *pos += 1; + + return 1; +} diff --git a/nest/aggregator.c b/nest/aggregator.c new file mode 100644 index 00000000..dfaae1dd --- /dev/null +++ b/nest/aggregator.c @@ -0,0 +1,671 @@ +/* + * BIRD Internet Routing Daemon -- Route aggregation + * + * (c) 2023 + * + * 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/route.h" +#include "nest/protocol.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" +#include + +/* Context of &f_val comparison. */ +struct cmp_ctx { + const struct channel *c; + const struct network *net; + const int val_count; + u32 failed:1; +}; + +static 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_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); + } +} + +static int +get_dynamic_attr_count(const struct aggr_item_linearized *ail) +{ + int ea_count = 0; + + for (int i = 0; i < ail->count; i++) + if (ail->items[i].type == AGGR_ITEM_DYNAMIC_ATTR) + ea_count++; + + return ea_count; +} + +/* + * Copy static and dynamic attributes from @old to @new according to + * aggregation list @ail. Because route may not have any extended + * attributes, return real number of attributes that were copied. + */ +static int +prepare_rta(struct rta *new, const struct rta *old, const struct aggr_item_linearized *ail) +{ + int pos = 0; + + for (int i = 0; i < ail->count; i++) + { + if (ail->items[i].type == AGGR_ITEM_DYNAMIC_ATTR) + { + u32 ea_code = ail->items[i].da.ea_code; + const struct eattr *e = ea_find(old->eattrs, ea_code); + + if (e) + new->eattrs->attrs[pos++] = *e; + } + else if (ail->items[i].type == AGGR_ITEM_STATIC_ATTR) + rta_set_static_attr(new, old, ail->items[i].sa); + } + + return pos; +} + +/* + * Find route with lowest ID in a sequence of rte_val_list entries + * within range [start, start + length). + * @start: first element in a sequence of equivalent routes + */ +static const struct rte * +find_rte_lowest_id(const struct rte_val_list **start, int length) +{ + const struct rte *rte = start[0]->rte; + u32 id = rte->src->global_id; + + for (int i = 1; i < length; i++) + { + u32 current = start[i]->rte->src->global_id; + + if (current < id) + { + id = current; + rte = start[i]->rte; + } + } + + log("Lowest ID: %d", id); + return rte; +} + +static int +compare_f_val(const struct f_val *v1, const struct f_val *v2, struct cmp_ctx *ctx) +{ + int result = val_compare(v1, v2); + + if (result != F_CMP_ERROR) + return result; + + ctx->failed = 1; + + struct buffer buf; + LOG_BUFFER_INIT(buf); + + buffer_puts(&buf, "v1 = "); + val_format(v1, &buf); + buffer_puts(&buf, ", v2 = "); + val_format(v2, &buf); + log(L_WARN "%s.%s: Error comparing values while aggregating routes to %N: %s", + ctx->c->proto->name, ctx->c->name, ctx->net->n.addr, buf.start); + + bug("Sorting routes according to aggregation list: F_CMP_ERROR"); +} + +/* + * Compare list of &f_val entries. + * @count: number of &f_val entries + */ +static int +compare_val_list(const struct f_val *v1, const struct f_val *v2, struct cmp_ctx *ctx) +{ + for (int i = 0; i < ctx->val_count; i++) + { + int res = compare_f_val(&v1[i], &v2[i], ctx); + if (res != 0) + return res; + } + + return 0; +} + +/* + * Comparator function for sorting array of pointers to &rte_val_list structures. + * Compare lists of &f_val associated with routes. + * If all values are equal, compare route's global IDs. + * @count: pointer to number of f_val entries + */ +static int +compare_val_list_id(const void *fst, const void *snd, void *context) +{ + struct cmp_ctx *ctx = (struct cmp_ctx *)context; + + for (int i = 0; i < ctx->val_count; i++) + { + /* + * This function receives two void pointers. + * Since we are sorting array of pointers, we have to cast this void + * pointer to struct rte_val_list** (pointer to first array element, + * which is a pointer). Dereference it once to access this element, + * which is struct rte_val_list*. Finally access f_val at position i + * and take its address, thus getting struct f_val*. + */ + const struct f_val *v1 = &(*(struct rte_val_list **)fst)->values[i]; + const struct f_val *v2 = &(*(struct rte_val_list **)snd)->values[i]; + int result = compare_f_val(v1, v2, ctx); + + if (result != 0) + return result; + } + + u32 id1 = (*(struct rte_val_list **)fst)->rte->src->global_id; + u32 id2 = (*(struct rte_val_list **)snd)->rte->src->global_id; + return id1 < id2 ? -1 : 1; +} + +/* + * Sort array of pointers to &rte_val_list entries. + * @rte_val: first element in array of pointers to &rte_val_list + * @rte_count: number of &rte_val_list entries + * @val_count: number of &f_val entries in each &rte_val_list entry + */ +static void +sort_rte_val_list(const struct rte_val_list **rte_val, int rte_count, struct cmp_ctx *ctx) +{ + log("======== Sorting routes... ========"); + qsort_r(rte_val, rte_count, sizeof(struct rte_val_list *), compare_val_list_id, (void *)ctx); + + for (int i = 0; i < rte_count; i++) + log("route ID: %d", rte_val[i]->rte->src->global_id); +} + +/* + * 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 +create_merged_rte(struct channel *c, struct network *net, const struct rte_val_list **rte_val, + int length, const struct aggr_item_linearized *ail, int refeed) +{ + const struct rte *old = rte_val[0]->rte; + const struct rta *rta_old = old->attrs; + struct rta *rta_new = allocz(rta_size(rta_old)); + + int ea_count = get_dynamic_attr_count(ail); + struct ea_list *eal = allocz(sizeof(struct ea_list) + sizeof(struct eattr) * ea_count); + + rta_new->dest = RTD_UNREACHABLE; + rta_new->eattrs = eal; + eal->next = NULL; + eal->count = prepare_rta(rta_new, rta_old, ail); + + const struct rte *rte_lowest = find_rte_lowest_id(rte_val, length); + struct rte *new = rte_get_temp(rta_new, rte_lowest->src); + new->net = net; + + do_rt_notify(c, net, new, NULL, refeed); + log("=============== CREATE MERGED ROUTE ==============="); + log("New route created: id = %d, protocol: %s", new->src->global_id, new->src->proto->name); + log("==================================================="); + + struct rte_block *rb = allocz(sizeof(struct rte_block) + sizeof(struct rte *) * length); + rb->ad.length = sizeof(struct rte_block) + sizeof(struct rte *) * length - sizeof(struct adata); + + for (int i = 0; i < length; i++) + rb->routes[i] = (struct rte *)rte_val[i]->rte; + + struct f_val val = { + .type = T_ROUTES_BLOCK, + .val.ad = &rb->ad, + }; + + f_run_val(ail->merge_filter, &new, rte_update_pool, &val, 0); +} + +/* + * Iterate through &rte_val_list entries and identify all sequences of + * equivalent routes. + * @rte_count: total number of routes being processed + * @val_count: number of &f_val entries with each route + * @ail: aggregation list + */ +static void +process_rte_list(struct channel *c, struct network *net, const struct rte_val_list **rte_val, + int rte_count, int val_count, const struct aggr_item_linearized *ail, int refeed) +{ + if (rte_count == 1) + { + create_merged_rte(c, net, rte_val, 1, ail, refeed); + return; + } + + struct cmp_ctx ctx = { + .c = c, + .net = net, + .val_count = val_count, + .failed = 0, + }; + + /* + * &start and ¤t are initially indices to first and second of + * &rte_val_list entries. If these entries contain equivalent routes, + * ¤t is incremented until non-equivalent route is found. + * [start, current) then define a range of routes that are to be merged. + * When non-equivalent route is found, &start is updated and the process + * continues until all entries are processed. + */ + int start = 0; + int current = 1; + log("RTE count: %d", rte_count); + log("VAL count: %d", val_count); + + while (start < rte_count && current < rte_count) + { + int res = compare_val_list(&rte_val[start]->values[0], &rte_val[current]->values[0], &ctx); + + /* At least two equivalent routes were found, try to find more. */ + if (res == 0) + { + int merged = 1; + + while (current < rte_count && res == 0) + { + log("Routes %d and %d are equal", rte_val[start]->rte->src->global_id, rte_val[current]->rte->src->global_id); + current++; + merged++; + + if (current < rte_count) + res = compare_val_list(&rte_val[start]->values[0], &rte_val[current]->values[0], &ctx); + } + + log("Creating merged route from %d routes", merged); + create_merged_rte(c, net, &rte_val[start], merged, ail, refeed); + start = current; + current++; + } + else + { + log("Route %d and %d are NOT equal", rte_val[start]->rte->src->global_id, rte_val[current]->rte->src->global_id); + log("Creating standalone route from route %d", rte_val[start]->rte->src->global_id); + create_merged_rte(c, net, &rte_val[start], 1, ail, refeed); + start = current; + current++; + } + } + + if (start < rte_count) + { + log("Creating standalone route from route %d", rte_val[start]->rte->src->global_id); + create_merged_rte(c, net, &rte_val[start], 1, ail, refeed); + } +} + +static int +get_rte_count(const struct rte *rte) +{ + int count = 0; + for (; rte; rte = rte->next) + count++; + return count; +} + +static void +log_attributes(const struct f_val *val, int count) +{ + struct buffer buf; + LOG_BUFFER_INIT(buf); + + for (int i = 0; i < count; i++) + { + val_format(&val[i], &buf); + log("%s", buf.start); + } +} + +/* + * 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_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 +} + +void +rt_notify_aggregated(struct channel *c, struct network *net, struct rte *new_changed, struct rte *old_changed, + struct rte *new_best, struct rte *old_best, int refeed) +{ + const struct aggr_item_linearized *ail = c->ai_aggr; + const int attr_count = ail->count; + + if (net->routes == NULL) + return; + + struct rte *best0 = net->routes; + const int rte_count = get_rte_count(best0); + + if (rte_count == 0) + return; + + log("---- RT NOTIFY AGGREGATED ----"); + log("Routes count: %d", rte_count); + log("Aggregation list attributes count: %d", attr_count); + log("aggr_item_linearized: %p", ail); + + struct rte **rte_temp = allocz(sizeof(struct rte *) * rte_count); + struct rte **rte_free_temp = allocz(sizeof(struct rte *) * rte_count); + + int rte_temp_count = 0; + int rte_free_count = 0; + + /* Run filter for all routes before aggregation. */ + for (struct rte *rt0 = best0; rt0; rt0 = rt0->next) + { + struct rte *rte_free = NULL; + struct rte *filtered = export_filter(c, rt0, &rte_free, 0); + + if (filtered) + rte_temp[rte_temp_count++] = filtered; + + if (rte_free) + rte_free_temp[rte_free_count++] = rte_free; + } + + const struct rte_val_list **rte_val_list_ptrs = allocz(sizeof(struct rte_val_list *) * rte_count); + int rte_val_list_pos = 0; + + for (int rte_idx = 0; rte_idx < rte_temp_count; rte_idx++) + { + struct rte *rt0 = rte_temp[rte_idx]; + struct rte_val_list *rte_val = allocz(sizeof(struct rte_val_list) + sizeof(struct f_val) * attr_count); + + rte_val->rte = rt0; + rte_val_list_ptrs[rte_val_list_pos++] = rte_val; + + for (int val_idx = 0; val_idx < attr_count; val_idx++) + { + int type = ail->items[val_idx].type; + + /* Evaluate route attributes. */ + switch (type) + { + case AGGR_ITEM_TERM: { + const struct f_line *line = ail->items[val_idx].line; + struct rte *rt1 = rt0; + enum filter_return fret = f_aggr_eval_line(line, &rt1, rte_update_pool, &rte_val->values[val_idx]); + + if (rt1 != rt0) + { + rte_free(rt1); + log(L_WARN "rt1 != rt0"); + } + + 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 = &rte_val->values[val_idx]; + eval_static_attr(rt0, ail->items[val_idx].sa, pos); + break; + } + + case AGGR_ITEM_DYNAMIC_ATTR: { + struct f_val *pos = &rte_val->values[val_idx]; + eval_dynamic_attr(rt0, ail->items[val_idx].da, pos); + break; + } + + default: + break; + } + } + + log_attributes(&rte_val->values[0], attr_count); + } + + struct cmp_ctx ctx = { + .c = c, + .net = net, + .val_count = attr_count, + .failed = 0, + }; + + sort_rte_val_list(rte_val_list_ptrs, rte_temp_count, &ctx); + + if (ctx.failed) + log(L_WARN "%s.%s: Could not aggregate routes to %N due to previous errors", c->proto->name, c->name, net->n.addr); + else + process_rte_list(c, net, rte_val_list_ptrs, rte_temp_count, attr_count, ail, refeed); + + for (int i = 0; i < rte_free_count; i++) + rte_free(rte_free_temp[i]); +} + diff --git a/nest/attrs.h b/nest/attrs.h index fcd5ac16..183f3893 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) @@ -65,24 +66,6 @@ static inline struct adata *as_path_prepend(struct linpool *pool, const struct a #define PM_ASN_SET 5 #define PM_LOOP 6 -struct f_path_mask_item { - union { - u32 asn; /* PM_ASN */ - const struct f_line *expr; /* PM_ASN_EXPR */ - const struct f_tree *set; /* PM_ASN_SET */ - struct { /* PM_ASN_RANGE */ - u32 from; - u32 to; - }; - }; - int kind; -}; - -struct f_path_mask { - uint len; - struct f_path_mask_item item[0]; -}; - int as_path_match(const struct adata *path, const struct f_path_mask *mask); @@ -157,9 +140,15 @@ static inline int ec_set_get_size(const struct adata *list) static inline int lc_set_get_size(const struct adata *list) { return list->length / 12; } +static inline int rte_set_get_size(const struct adata *list) +{ return list->length / sizeof(struct rte *); } + static inline u32 *int_set_get_data(const struct adata *list) { return (u32 *) list->data; } +static inline struct rte *rte_set_get_data(const struct adata *list, u32 idx) +{ return ((struct rte_block *)list)->routes[idx]; } + static inline u32 ec_hi(u64 ec) { return ec >> 32; } static inline u32 ec_lo(u64 ec) { return ec; } @@ -184,13 +173,6 @@ static inline u64 ec_ip4(enum ec_subtype kind, u64 key, u64 val) static inline u64 ec_generic(u64 key, u64 val) { return (key << 32) | val; } -/* Large community value */ -typedef struct lcomm { - u32 asn; - u32 ldp1; - u32 ldp2; -} lcomm; - #define LCOMM_LENGTH 12 static inline lcomm lc_get(const u32 *l, int i) @@ -238,6 +220,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/config.Y b/nest/config.Y index 5626d137..bcd238d6 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -16,6 +16,7 @@ CF_HDR #include "lib/mac.h" CF_DEFINES +#define AGGR_ITEM_ALLOC cfg_allocz(sizeof(struct aggr_item)) static struct rtable_config *this_table; static struct proto_config *this_proto; @@ -126,6 +127,7 @@ CF_KEYWORDS(GRACEFUL, RESTART, WAIT, MAX, AS) CF_KEYWORDS(MIN, IDLE, RX, TX, INTERVAL, MULTIPLIER, PASSIVE) CF_KEYWORDS(CHECK, LINK) CF_KEYWORDS(SORTED, TRIE, MIN, MAX, SETTLE, TIME, GC, THRESHOLD, PERIOD) +CF_KEYWORDS(AGGREGATE, ON, MERGE, BY) /* For r_args_channel */ CF_KEYWORDS(IPV4, IPV4_MC, IPV4_MPLS, IPV6, IPV6_MC, IPV6_MPLS, IPV6_SADR, VPN4, VPN4_MC, VPN4_MPLS, VPN6, VPN6_MC, VPN6_MPLS, ROA4, ROA6, FLOW4, FLOW6, MPLS, PRI, SEC) @@ -151,6 +153,8 @@ CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6) %type r_args_for %type channel_sym %type channel_arg +%type aggr_item aggr_list +%type aggr_definition CF_GRAMMAR @@ -298,6 +302,111 @@ proto_item: ; +aggr_list: + aggr_item + | aggr_list ',' aggr_item { + if ($3 == NULL) { + $$ = $1; + } else { + $$ = $3; + $$->next = $1; + } + } + ; + +aggr_item: + '(' term ')' { + $$ = AGGR_ITEM_ALLOC; + $$->internal.type = AGGR_ITEM_TERM; + $$->internal.line = f_linearize($2, 1); + } + | CF_SYM_KNOWN { + switch ($1->class) { + case SYM_ATTRIBUTE: + $$ = AGGR_ITEM_ALLOC; + $$->internal.type = AGGR_ITEM_DYNAMIC_ATTR; + $$->internal.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; + $$->internal.type = AGGR_ITEM_DYNAMIC_ATTR; + $$->internal.da = $1; + } + | static_attr { + $$ = AGGR_ITEM_ALLOC; + $$->internal.type = AGGR_ITEM_STATIC_ATTR; + $$->internal.sa = $1; + } + ; + +aggr_definition: + AGGREGATE ON aggr_list MERGE BY filter { + _Bool net_present = 0; + int count = 0; + + for (const struct aggr_item *item = $3; item; item = item->next) { + if (item->internal.type == AGGR_ITEM_STATIC_ATTR && item->internal.sa.sa_code == SA_NET) { + net_present = 1; + continue; + } + + count++; + } + + if (!net_present) + cf_error("'NET' must be present"); + + size_t allocated = sizeof(struct aggr_item_linearized) + sizeof(struct aggr_item_internal) * count; + struct aggr_item_linearized *linear = cfg_allocz(allocated); + + log("nest/config.Y: linear: %p, allocated: %d, count: %d", linear, allocated, count); + + int pos = 0; + for (const struct aggr_item *item = $3; item; item = item->next) { + if (item->internal.type == AGGR_ITEM_STATIC_ATTR && item->internal.sa.sa_code == SA_NET) + continue; + + linear->items[pos++] = item->internal; + } + + linear->count = pos; + linear->merge_filter = $6; + + int node = 1; + + if (!linear) { + for (int i = 0; i < linear->count; i++) { + switch (linear->items[i].type) { + case AGGR_ITEM_TERM: + log("node %d, type: term", node); + break; + case AGGR_ITEM_STATIC_ATTR: + log("node %d, type: static", node); + break; + case AGGR_ITEM_DYNAMIC_ATTR: + log("node %d, type: dynamic", node); + break; + default: + log("node %d, type: other", node); + break; + } + + node++; + } + } + + $$ = linear; + } + ; + + channel_start: net_type { $$ = this_channel = channel_config_get(NULL, net_label[$1], $1, this_proto); @@ -311,6 +420,7 @@ channel_item_: } | IMPORT imexport { this_channel->in_filter = $2; } | EXPORT imexport { this_channel->out_filter = $2; } + | EXPORT imexport aggr_definition { this_channel->out_filter = $2; this_channel->ai_aggr = $3; this_channel->ra_mode = RA_AGGREGATED; } | RECEIVE LIMIT limit_spec { this_channel->rx_limit = $3; } | IMPORT LIMIT limit_spec { this_channel->in_limit = $3; } | EXPORT LIMIT limit_spec { this_channel->out_limit = $3; } diff --git a/nest/proto.c b/nest/proto.c index 65714c94..5b435f29 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -90,6 +90,40 @@ proto_log_state_change(struct proto *p) p->last_state_name_announced = NULL; } +static int +aggr_item_same(const struct aggr_item_internal *fst, const struct aggr_item_internal *snd) +{ + if (fst->type != snd->type) + return 0; + + switch (fst->type) + { + case AGGR_ITEM_TERM: + return f_same(fst->line, snd->line); + case AGGR_ITEM_STATIC_ATTR: + return memcmp(&fst->sa, &snd->sa, sizeof(struct f_static_attr)) == 0; + case AGGR_ITEM_DYNAMIC_ATTR: + return memcmp(&fst->da, &snd->da, sizeof(struct f_dynamic_attr)) == 0; + default: + bug("Broken aggregating data"); + } +} + +static int +aggr_item_linearized_same(const struct aggr_item_linearized *fst, const struct aggr_item_linearized *snd) +{ + if (!fst || !snd) + return 0; + + if (fst->count != snd->count) + return 0; + + for (int i = 0; i < fst->count; i++) + if (!aggr_item_same(&fst->items[i], &snd->items[i])) + return 0; + + return 1; +} struct channel_config * proto_cf_find_channel(struct proto_config *pc, uint net_type) @@ -168,6 +202,7 @@ proto_add_channel(struct proto *p, struct channel_config *cf) c->in_filter = cf->in_filter; c->out_filter = cf->out_filter; + c->ai_aggr = cf->ai_aggr; c->rx_limit = cf->rx_limit; c->in_limit = cf->in_limit; c->out_limit = cf->out_limit; @@ -840,6 +875,14 @@ channel_reconfigure(struct channel *c, struct channel_config *cf) /* Note that filter_same() requires arguments in (new, old) order */ int import_changed = !filter_same(cf->in_filter, c->in_filter); int export_changed = !filter_same(cf->out_filter, c->out_filter); + + if (cf->ra_mode == RA_AGGREGATED) + { + export_changed |= !aggr_item_linearized_same(cf->ai_aggr, c->ai_aggr); + export_changed |= !filter_same(cf->ai_aggr->merge_filter, c->ai_aggr->merge_filter); + c->ai_aggr = cf->ai_aggr; + } + int rpki_reload_changed = (cf->rpki_reload != c->rpki_reload); if (c->preference != cf->preference) @@ -852,6 +895,7 @@ channel_reconfigure(struct channel *c, struct channel_config *cf) c->in_filter = cf->in_filter; c->out_filter = cf->out_filter; c->rx_limit = cf->rx_limit; + c->ai_aggr = cf->ai_aggr; c->in_limit = cf->in_limit; c->out_limit = cf->out_limit; diff --git a/nest/protocol.h b/nest/protocol.h index da6d434e..01d9cf73 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -487,6 +487,7 @@ struct channel_config { struct proto_config *parent; /* Where channel is defined (proto or template) */ struct rtable_config *table; /* Table we're attached to */ const struct filter *in_filter, *out_filter; /* Attached filters */ + const struct aggr_item_linearized *ai_aggr; struct channel_limit rx_limit; /* Limit for receiving routes from protocol (relevant when in_keep_filtered is active) */ struct channel_limit in_limit; /* Limit for importing routes from protocol */ @@ -513,6 +514,7 @@ struct channel { struct rtable *table; const struct filter *in_filter; /* Input filter */ const struct filter *out_filter; /* Output filter */ + const struct aggr_item_linearized *ai_aggr; struct bmap export_map; /* Keeps track which routes passed export filter */ struct channel_limit rx_limit; /* Receive limit (for in_keep_filtered) */ struct channel_limit in_limit; /* Input limit */ diff --git a/nest/route.h b/nest/route.h index 7aec7117..9169a64c 100644 --- a/nest/route.h +++ b/nest/route.h @@ -14,6 +14,8 @@ #include "lib/resource.h" #include "lib/net.h" +#include "filter/data.h" + struct ea_list; struct protocol; struct proto; @@ -284,6 +286,7 @@ static inline int rte_is_filtered(rte *r) { return !!(r->flags & REF_FILTERED); #define RA_ACCEPTED 2 /* Announcement of first accepted route */ #define RA_ANY 3 /* Announcement of any route change */ #define RA_MERGED 4 /* Announcement of optimal route merged with next ones */ +#define RA_AGGREGATED 5 /* Announcement of merged routes */ /* Return value of preexport() callback */ #define RIC_ACCEPT 1 /* Accepted by protocol */ @@ -322,6 +325,7 @@ void rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src /* rte_update() moved to protocol.h to avoid dependency conflicts */ int rt_examine(rtable *t, net_addr *a, struct channel *c, const struct filter *filter); rte *rt_export_merged(struct channel *c, net *net, rte **rt_free, linpool *pool, int silent); +rte *rt_export_aggregated(struct channel *c, net *net, rte **rt_free, linpool *pool, int silent); void rt_refresh_begin(rtable *t, struct channel *c); void rt_refresh_end(rtable *t, struct channel *c); void rt_modify_stale(rtable *t, struct channel *c); @@ -546,13 +550,6 @@ const char *ea_custom_name(uint ea); #define EAF_EMBEDDED 0x01 /* Data stored in eattr.u.data (part of type spec) */ #define EAF_VAR_LENGTH 0x02 /* Attribute length is variable (part of type spec) */ -typedef struct adata { - uint length; /* Length of data */ - byte data[0]; -} adata; - -extern const adata null_adata; /* adata of length 0 */ - static inline struct adata * lp_alloc_adata(struct linpool *pool, uint len) { @@ -762,4 +759,50 @@ int rt_flowspec_check(rtable *tab_ip, rtable *tab_flow, const net_addr *n, rta * #define ROA_VALID 1 #define ROA_INVALID 2 +/* + * Aggregating routes + */ + +enum aggr_item_type { + AGGR_ITEM_TERM, + AGGR_ITEM_STATIC_ATTR, + AGGR_ITEM_DYNAMIC_ATTR, +}; + +struct aggr_item_internal { + enum aggr_item_type type; + union { + struct f_static_attr sa; + struct f_dynamic_attr da; + const struct f_line *line; + }; +}; + +struct aggr_item { + const struct aggr_item *next; + struct aggr_item_internal internal; +}; + +struct aggr_item_linearized { + int count; + const struct filter *merge_filter; + struct aggr_item_internal items[]; +}; + +struct rte_val_list { + const struct rte *rte; + struct f_val values[]; +}; + +void rt_notify_aggregated(struct channel *c, struct network *net, struct rte *new_changed, struct rte *old_changed, + struct rte *new_best, struct rte *old_best, int refeed); + +rte *export_filter(struct channel *c, struct rte *rt0, struct rte **rt_free, int silent); + +/* + * This is an internal function of the nest. It is not supposed to be used + * directly. This is a temporary solution. + */ +void do_rt_notify(struct channel *c, struct network *net, struct rte *new, struct rte *old, int refeed); + #endif diff --git a/nest/rt-show.c b/nest/rt-show.c index 183d023c..7c2916ab 100644 --- a/nest/rt-show.c +++ b/nest/rt-show.c @@ -143,19 +143,28 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d) else if ((d->export_mode == RSEM_EXPORT) && (ec->ra_mode == RA_MERGED)) { /* Special case for merged export */ - rte *rt_free; - e = rt_export_merged(ec, n, &rt_free, c->show_pool, 1); + rte *rte_free; + e = rt_export_merged(ec, n, &rte_free, c->show_pool, 1); pass = 1; if (!e) { e = ee; goto skip; } } + else if ((d->export_mode == RSEM_EXPORT) && (ec->ra_mode == RA_AGGREGATED)) + { + rte *rte_free; + e = rt_export_aggregated(ec, n, &rte_free, c->show_pool, 1); + pass = 1; + + if (!e) + { e = ee; goto skip; } + } else if (d->export_mode) { struct proto *ep = ec->proto; int ic = ep->preexport ? ep->preexport(ec, e) : 0; - if (ec->ra_mode == RA_OPTIMAL || ec->ra_mode == RA_MERGED) + if (ec->ra_mode == RA_OPTIMAL || ec->ra_mode == RA_MERGED || ec->ra_mode == RA_AGGREGATED) pass = 1; if (ic < 0) diff --git a/nest/rt-table.c b/nest/rt-table.c index 8e621ee4..e64e7a96 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -94,6 +94,10 @@ #undef LOCAL_DEBUG +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + #include "nest/bird.h" #include "nest/route.h" #include "nest/protocol.h" @@ -109,6 +113,7 @@ #include "lib/string.h" #include "lib/alloca.h" #include "lib/flowspec.h" +#include #ifdef CONFIG_BGP #include "proto/bgp/bgp.h" @@ -749,13 +754,13 @@ export_filter_(struct channel *c, rte *rt0, rte **rt_free, linpool *pool, int si return NULL; } -static inline rte * +rte * export_filter(struct channel *c, rte *rt0, rte **rt_free, int silent) { return export_filter_(c, rt0, rt_free, rte_update_pool, silent); } -static void +void do_rt_notify(struct channel *c, net *net, rte *new, rte *old, int refeed) { struct proto *p = c->proto; @@ -975,6 +980,57 @@ rt_export_merged(struct channel *c, net *net, rte **rt_free, linpool *pool, int return best; } +rte * +rt_export_aggregated(struct channel *c, net *net, rte **rt_free, linpool *pool, int silent) +{ + // struct proto *p = c->proto; + struct nexthop *nhs = NULL; + rte *best0, *best, *rt0, *rt, *tmp; + + best0 = net->routes; + *rt_free = NULL; + + if (!rte_is_valid(best0)) + return NULL; + + best = export_filter_(c, best0, rt_free, pool, silent); + + if (!best || !rte_is_reachable(best)) + return best; + + for (rt0 = best0->next; rt0; rt0 = rt0->next) + { + if (!rte_mergable(best0, rt0)) + continue; + + rt = export_filter_(c, rt0, &tmp, pool, 1); + + if (!rt) + continue; + + if (rte_is_reachable(rt)) + nhs = nexthop_merge_rta(nhs, rt->attrs, pool, c->merge_limit); + + if (tmp) + rte_free(tmp); + } + + if (nhs) + { + nhs = nexthop_merge_rta(nhs, best->attrs, pool, c->merge_limit); + + if (nhs->next) + { + best = rte_cow_rta(best, pool); + nexthop_link(best->attrs, nhs); + } + } + + if (best != best0) + *rt_free = best; + + return best; +} static void rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed, @@ -1019,7 +1075,6 @@ rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed rte_free(new_free); } - /** * rte_announce - announce a routing table change * @tab: table the route has been added to @@ -1135,6 +1190,10 @@ rte_announce(rtable *tab, uint type, net *net, rte *new, rte *old, case RA_MERGED: rt_notify_merged(c, net, new, old, new_best, old_best, 0); break; + + case RA_AGGREGATED: + rt_notify_aggregated(c, net, new, old, new_best, old_best, 0); + break; } } } @@ -2969,6 +3028,8 @@ do_feed_channel(struct channel *c, net *n, rte *e) rt_notify_accepted(c, n, NULL, NULL, c->refeeding); else if (c->ra_mode == RA_MERGED) rt_notify_merged(c, n, NULL, NULL, e, e, c->refeeding); + else if (c->ra_mode == RA_AGGREGATED) + rt_notify_aggregated(c, n, NULL, NULL, e, e, c->refeeding); else /* RA_BASIC */ rt_notify_basic(c, n, e, e, c->refeeding); rte_update_unlock(); @@ -3008,7 +3069,8 @@ rt_feed_channel(struct channel *c) if ((c->ra_mode == RA_OPTIMAL) || (c->ra_mode == RA_ACCEPTED) || - (c->ra_mode == RA_MERGED)) + (c->ra_mode == RA_MERGED) || + (c->ra_mode == RA_AGGREGATED)) if (rte_is_valid(e)) { /* In the meantime, the protocol may fell down */ diff --git a/proto/pipe/config.Y b/proto/pipe/config.Y index 1202c169..96d75f94 100644 --- a/proto/pipe/config.Y +++ b/proto/pipe/config.Y @@ -9,6 +9,7 @@ CF_HDR #include "proto/pipe/pipe.h" +#include "nest/route.h" CF_DEFINES @@ -16,7 +17,7 @@ CF_DEFINES CF_DECLS -CF_KEYWORDS(PIPE, PEER, TABLE) +CF_KEYWORDS(PIPE, PEER, TABLE, AGGREGATED, IMPORT, EXPORT, AGGREGATE) CF_GRAMMAR @@ -41,6 +42,8 @@ pipe_proto: | pipe_proto proto_item ';' | pipe_proto channel_item_ ';' | pipe_proto PEER TABLE rtable ';' { PIPE_CFG->peer = $4; } + | pipe_proto MERGE PATHS IMPORT bool kern_mp_limit { PIPE_CFG->config_import.limit = $5 ? $6 : 0; PIPE_CFG->config_import.use_aggregator = 0; } + | pipe_proto MERGE PATHS EXPORT bool kern_mp_limit { PIPE_CFG->config_export.limit = $5 ? $6 : 0; PIPE_CFG->config_export.use_aggregator = 0; } ; CF_CODE diff --git a/proto/pipe/pipe.c b/proto/pipe/pipe.c index d48ce387..e1b817cd 100644 --- a/proto/pipe/pipe.c +++ b/proto/pipe/pipe.c @@ -1,4 +1,4 @@ -/* + /* * BIRD -- Table-to-Table Routing Protocol a.k.a Pipe * * (c) 1999--2000 Martin Mares @@ -81,7 +81,7 @@ pipe_rt_notify(struct proto *P, struct channel *src_ch, net *n, rte *new, rte *o } src_ch->table->pipe_busy = 1; - rte_update2(dst, n->n.addr, e, src); + rte_update2(dst, n->n.addr, e, old ? old->src : new->src); src_ch->table->pipe_busy = 0; } @@ -138,13 +138,16 @@ pipe_configure_channels(struct pipe_proto *p, struct pipe_config *cf) { struct channel_config *cc = proto_cf_main_channel(&cf->c); + log("pipe configure channels: ra_mode: %d", cc->ra_mode); + log("pipe configure channels: ai_aggr: %p", cc->ai_aggr); struct channel_config pri_cf = { .name = "pri", .channel = cc->channel, .table = cc->table, .out_filter = cc->out_filter, .in_limit = cc->in_limit, - .ra_mode = RA_ANY, + .ra_mode = cc->ra_mode, + .ai_aggr = cc->ai_aggr, .debug = cc->debug, .rpki_reload = cc->rpki_reload, }; @@ -160,6 +163,30 @@ pipe_configure_channels(struct pipe_proto *p, struct pipe_config *cf) .rpki_reload = cc->rpki_reload, }; + log("ai_aggr = %p", cc->ai_aggr); + const struct aggr_item_linearized *ail = cc->ai_aggr; + int node = 1; + + if (ail != NULL) { + for (int i = 0; i < ail->count; i++) { + switch (ail->items[i].type) { + case AGGR_ITEM_TERM: + log("node %d, type: term", node); + break; + case AGGR_ITEM_STATIC_ATTR: + log("node %d, type: static", node); + break; + case AGGR_ITEM_DYNAMIC_ATTR: + log("node %d, type: dynamic", node); + break; + default: + log("node %d, type: other", node); + break; + } + node++; + } + } + return proto_configure_channel(&p->p, &p->pri, &pri_cf) && proto_configure_channel(&p->p, &p->sec, &sec_cf); diff --git a/proto/pipe/pipe.h b/proto/pipe/pipe.h index 038c6666..ebb5b3ae 100644 --- a/proto/pipe/pipe.h +++ b/proto/pipe/pipe.h @@ -9,9 +9,21 @@ #ifndef _BIRD_PIPE_H_ #define _BIRD_PIPE_H_ +struct merging_import { + uint limit; + uint use_aggregator; +}; + +struct merging_export { + uint limit; + uint use_aggregator; +}; + struct pipe_config { struct proto_config c; - struct rtable_config *peer; /* Table we're connected to */ + struct rtable_config *peer; /* Table we're connected to */ + struct merging_import config_import; /* From peer table to primary table */ + struct merging_export config_export; /* From primary table to peer table */ }; struct pipe_proto { diff --git a/sysdep/unix/krt.c b/sysdep/unix/krt.c index 9f95247f..aaa59061 100644 --- a/sysdep/unix/krt.c +++ b/sysdep/unix/krt.c @@ -581,6 +581,9 @@ krt_export_net(struct krt_proto *p, net *net, rte **rt_free) if (c->ra_mode == RA_MERGED) return rt_export_merged(c, net, rt_free, krt_filter_lp, 1); + if (c->ra_mode == RA_AGGREGATED) + return rt_export_aggregated(c, net, rt_free, krt_filter_lp, 1); + rt = net->routes; *rt_free = NULL;