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@
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))
struct timeformat *tf;
mpls_label_stack *mls;
const struct adata *bs;
+ struct aggr_item *ai;
+ struct aggr_item_linearized *ail;
}
%token END CLI_MARKER INVALID_TOKEN ELSECOL DDOT
[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
{
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");
}
}
ACCEPT, REJECT, ERROR,
INT, BOOL, IP, PREFIX, RD, PAIR, QUAD, EC, LC,
SET, STRING, BYTESTRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST,
+ ROUTES,
IF, THEN, ELSE, CASE,
FOR, DO,
TRUE, FALSE, RT, RO, UNKNOWN, GENERIC,
| CLIST { $$ = T_CLIST; }
| ECLIST { $$ = T_ECLIST; }
| LCLIST { $$ = T_LCLIST; }
+ | ROUTE { $$ = T_ROUTE; }
| type SET {
switch ($1) {
case T_INT:
$$ = 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);
| 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
| term_bs
| 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);
+ }
;
term_bs:
[T_LC] = "lc",
[T_LCLIST] = "lclist",
[T_RD] = "rd",
+
+ [T_ROUTE] = "route",
+ [T_ROUTES_BLOCK] = "block of routes",
};
const char *
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;
};
}
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;
}
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);
}
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
*/
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;
}
}
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;
enum f_type args_type[];
};
+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_* */
const struct adata *ad;
const struct f_path_mask *path_mask;
struct f_path_mask_item pmi;
+ struct rte *rte;
} val;
};
/* Filter l-value */
struct f_lval {
enum f_lval_type type;
+ struct f_inst *rte;
union {
struct symbol *sym;
struct f_dynamic_attr da;
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);
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, T_PATH, ad, &null_adata);
METHOD_R(T_CLIST, empty, T_CLIST, ad, &null_adata);
METHOD_R(T_ECLIST, empty, T_ECLIST, ad, &null_adata);
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)
}
}
- INST(FI_RTA_GET, 0, 1) {
+ INST(FI_RTA_GET, 1, 1) {
{
- STATIC_ATTR;
ACCESS_RTE;
- struct rta *rta = (*fs->rte)->attrs;
+ ARG(1, T_ROUTE);
+ STATIC_ATTR;
+
+ struct rta *rta = v1.val.rte ? v1.val.rte->attrs : (*fs->rte)->attrs;
switch (sa.sa_code)
{
}
}
- INST(FI_EA_GET, 0, 1) { /* Access to extended attributes */
- DYNAMIC_ATTR;
+ INST(FI_EA_GET, 1, 1) { /* Access to extended attributes */
ACCESS_RTE;
ACCESS_EATTRS;
+ ARG(1, T_ROUTE);
+ DYNAMIC_ATTR;
RESULT_TYPE(da.f_type);
{
- eattr *e = ea_find(*fs->eattrs, da.ea_code);
+ struct ea_list *eal = v1.val.rte ? v1.val.rte->attrs->eattrs : *fs->eattrs;
+ eattr *e = ea_find(eal, da.ea_code);
if (!e) {
RESULT_VAL(val_empty(da.f_type));
/* Buffer for log output */
struct buffer buf;
+ /* Pointers to routes we are aggregating */
+ const struct f_val *val;
+
/* Filter execution flags */
int flags;
};
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
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 );
.stack = &filter_stack,
.rte = rte,
.pool = tmp_pool,
+ .val = val,
.flags = flags,
};
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);
enum filter_return f_eval_buf(const struct f_line *expr, struct linpool *tmp_pool, buffer *buf);
-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)
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)
{
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;
+}
--- /dev/null
+/*
+ * 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 <stdlib.h>
+
+/* 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]);
+}
+
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)
#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);
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; }
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)
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 */
#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;
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)
%type <net_ptr> r_args_for
%type <t> channel_sym
%type <c> channel_arg
+%type <ai> aggr_item aggr_list
+%type <ail> aggr_definition
CF_GRAMMAR
;
+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);
}
| 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; }
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)
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;
/* 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)
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;
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 */
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 */
#include "lib/resource.h"
#include "lib/net.h"
+#include "filter/data.h"
+
struct ea_list;
struct protocol;
struct proto;
#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 */
/* 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);
#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)
{
#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
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)
#undef LOCAL_DEBUG
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
#include "nest/bird.h"
#include "nest/route.h"
#include "nest/protocol.h"
#include "lib/string.h"
#include "lib/alloca.h"
#include "lib/flowspec.h"
+#include <stdlib.h>
#ifdef CONFIG_BGP
#include "proto/bgp/bgp.h"
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;
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,
rte_free(new_free);
}
-
/**
* rte_announce - announce a routing table change
* @tab: table the route has been added to
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;
}
}
}
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();
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 */
CF_HDR
#include "proto/pipe/pipe.h"
+#include "nest/route.h"
CF_DEFINES
CF_DECLS
-CF_KEYWORDS(PIPE, PEER, TABLE)
+CF_KEYWORDS(PIPE, PEER, TABLE, AGGREGATED, IMPORT, EXPORT, AGGREGATE)
CF_GRAMMAR
| 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
-/*
+ /*
* BIRD -- Table-to-Table Routing Protocol a.k.a Pipe
*
* (c) 1999--2000 Martin Mares <mj@ucw.cz>
}
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;
}
{
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,
};
.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);
#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 {
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;