0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-12-22 09:41:54 +00:00
bird/nest/aggregator.c
Igor Putovny 8d186b2a22 Basic route aggregation
User can specify list of route attributes in the configuration file
and run route aggregation on the export side of the pipe protocol.
Routes are sorted and for every group of equivalent routes
new route is created and exported to the routing table.
It is also possible to specify filter which will run for every
route before aggregation.
Furthermore, it will be possible to set attributes of new routes
according to attributes of the aggregated routes.

This is a work in progress.
2023-06-21 13:36:24 +02:00

672 lines
19 KiB
C

/*
* 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 &current are initially indices to first and second of
* &rte_val_list entries. If these entries contain equivalent routes,
* &current 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]);
}