0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-12-22 09:41:54 +00:00
bird/filter/data.c
Igor Putovny 977b82fba4 Basic route aggregation
Add a new protocol offering 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.

Original work by Igor Putovny, subsequent cleanups and finalization by
Maria Matejka.
2023-09-26 15:46:24 +02:00

670 lines
16 KiB
C

/*
* Filters: utility functions
*
* (c) 1998 Pavel Machek <pavel@ucw.cz>
* (c) 2019 Maria Matejka <mq@jmq.cz>
*
* Can be freely distributed and used under the terms of the GNU GPL.
*
*/
#include "nest/bird.h"
#include "lib/lists.h"
#include "lib/resource.h"
#include "lib/socket.h"
#include "lib/string.h"
#include "lib/unaligned.h"
#include "lib/net.h"
#include "lib/ip.h"
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
#include "nest/attrs.h"
#include "conf/conf.h"
#include "filter/filter.h"
#include "filter/f-inst.h"
#include "filter/data.h"
static const char * const f_type_str[] = {
[T_VOID] = "void",
[T_NONE] = "none",
[T_INT] = "int",
[T_BOOL] = "bool",
[T_PAIR] = "pair",
[T_QUAD] = "quad",
[T_ENUM_RTS] = "enum rts",
[T_ENUM_BGP_ORIGIN] = "enum bgp_origin",
[T_ENUM_SCOPE] = "enum scope",
[T_ENUM_RTC] = "enum rtc",
[T_ENUM_RTD] = "enum rtd",
[T_ENUM_ROA] = "enum roa",
[T_ENUM_NETTYPE] = "enum nettype",
[T_ENUM_RA_PREFERENCE] = "enum ra_preference",
[T_ENUM_AF] = "enum af",
[T_IP] = "ip",
[T_NET] = "prefix",
[T_STRING] = "string",
[T_BYTESTRING] = "bytestring",
[T_PATH_MASK] = "bgpmask",
[T_PATH] = "bgppath",
[T_CLIST] = "clist",
[T_EC] = "ec",
[T_ECLIST] = "eclist",
[T_LC] = "lc",
[T_LCLIST] = "lclist",
[T_RD] = "rd",
[T_ROUTE] = "route",
[T_ROUTES_BLOCK] = "block of routes",
};
const char *
f_type_name(enum f_type t)
{
if (t < ARRAY_SIZE(f_type_str))
return f_type_str[t] ?: "?";
if ((t == T_SET) || (t == T_PREFIX_SET))
return "set";
return "?";
}
enum f_type
f_type_element_type(enum f_type t)
{
switch(t) {
case T_PATH: return T_INT;
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;
};
}
const struct f_trie f_const_empty_trie = { .ipv4 = -1, };
const struct f_val f_const_empty_prefix_set = {
.type = T_PREFIX_SET,
.val.ti = &f_const_empty_trie,
};
static struct adata *
adata_empty(struct linpool *pool, int l)
{
struct adata *res = lp_alloc(pool, sizeof(struct adata) + l);
res->length = l;
return res;
}
static void
pm_format(const struct f_path_mask *p, buffer *buf)
{
int loop = 0;
buffer_puts(buf, "[= ");
for (uint i=0; i<p->len; i++)
{
switch(p->item[i].kind)
{
case PM_ASN:
buffer_print(buf, "%u ", p->item[i].asn);
break;
case PM_QUESTION:
buffer_puts(buf, "? ");
break;
case PM_ASTERISK:
buffer_puts(buf, "* ");
break;
case PM_LOOP:
loop = 1;
break;
case PM_ASN_RANGE:
buffer_print(buf, "%u..%u ", p->item[i].from, p->item[i].to);
break;
case PM_ASN_SET:
tree_format(p->item[i].set, buf);
buffer_puts(buf, " ");
break;
case PM_ASN_EXPR:
ASSERT(0);
}
if (loop && (p->item[i].kind != PM_LOOP))
{
buffer_puts(buf, "+ ");
loop = 0;
}
}
buffer_puts(buf, "=]");
}
static inline int
lcomm_cmp(lcomm v1, lcomm v2)
{
if (v1.asn != v2.asn)
return (v1.asn > v2.asn) ? 1 : -1;
if (v1.ldp1 != v2.ldp1)
return (v1.ldp1 > v2.ldp1) ? 1 : -1;
if (v1.ldp2 != v2.ldp2)
return (v1.ldp2 > v2.ldp2) ? 1 : -1;
return 0;
}
/**
* val_compare - compare two values
* @v1: first value
* @v2: second value
*
* Compares two values and returns -1, 0, 1 on <, =, > or F_CMP_ERROR on
* error. Tree module relies on this giving consistent results so
* that it can be used for building balanced trees.
*/
int
val_compare(const struct f_val *v1, const struct f_val *v2)
{
if (v1->type != v2->type) {
if (v1->type == T_VOID) /* Hack for else */
return -1;
if (v2->type == T_VOID)
return 1;
/* IP->Quad implicit conversion */
if ((v1->type == T_QUAD) && val_is_ip4(v2))
return uint_cmp(v1->val.i, ipa_to_u32(v2->val.ip));
if (val_is_ip4(v1) && (v2->type == T_QUAD))
return uint_cmp(ipa_to_u32(v1->val.ip), v2->val.i);
DBG( "Types do not match in val_compare\n" );
return F_CMP_ERROR;
}
switch (v1->type) {
case T_VOID:
return 0;
case T_ENUM:
case T_INT:
case T_BOOL:
case T_PAIR:
case T_QUAD:
return uint_cmp(v1->val.i, v2->val.i);
case T_EC:
case T_RD:
return u64_cmp(v1->val.ec, v2->val.ec);
case T_LC:
return lcomm_cmp(v1->val.lc, v2->val.lc);
case T_IP:
return ipa_compare(v1->val.ip, v2->val.ip);
case T_NET:
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;
}
}
static inline int
bs_same(const struct adata *bs1, const struct adata *bs2)
{
return (bs1->length == bs2->length) && !memcmp(bs1->data, bs2->data, bs1->length);
}
static inline int
pmi_same(const struct f_path_mask_item *mi1, const struct f_path_mask_item *mi2)
{
if (mi1->kind != mi2->kind)
return 0;
switch (mi1->kind) {
case PM_ASN:
if (mi1->asn != mi2->asn)
return 0;
break;
case PM_ASN_EXPR:
if (!f_same(mi1->expr, mi2->expr))
return 0;
break;
case PM_ASN_RANGE:
if (mi1->from != mi2->from)
return 0;
if (mi1->to != mi2->to)
return 0;
break;
case PM_ASN_SET:
if (!same_tree(mi1->set, mi2->set))
return 0;
break;
}
return 1;
}
static int
pm_same(const struct f_path_mask *m1, const struct f_path_mask *m2)
{
if (m1->len != m2->len)
return 0;
for (uint i=0; i<m1->len; i++)
if (!pmi_same(&(m1->item[i]), &(m2->item[i])))
return 0;
return 1;
}
/**
* val_same - compare two values
* @v1: first value
* @v2: second value
*
* Compares two values and returns 1 if they are same and 0 if not.
* Comparison of values of different types is valid and returns 0.
*/
int
val_same(const struct f_val *v1, const struct f_val *v2)
{
int rc;
rc = val_compare(v1, v2);
if (rc != F_CMP_ERROR)
return !rc;
if (v1->type != v2->type)
return 0;
switch (v1->type) {
case T_BYTESTRING:
return bs_same(v1->val.bs, v2->val.bs);
case T_PATH_MASK:
return pm_same(v1->val.path_mask, v2->val.path_mask);
case T_PATH_MASK_ITEM:
return pmi_same(&(v1->val.pmi), &(v2->val.pmi));
case T_PATH:
case T_CLIST:
case T_ECLIST:
case T_LCLIST:
return adata_same(v1->val.ad, v2->val.ad);
case T_SET:
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);
}
}
int
clist_set_type(const struct f_tree *set, struct f_val *v)
{
if (!set)
{
v->type = T_VOID;
return 1;
}
switch (set->from.type)
{
case T_PAIR:
v->type = T_PAIR;
return 1;
case T_QUAD:
v->type = T_QUAD;
return 1;
case T_IP:
if (val_is_ip4(&(set->from)) && val_is_ip4(&(set->to)))
{
v->type = T_QUAD;
return 1;
}
/* Fall through */
default:
v->type = T_VOID;
return 0;
}
}
int
clist_match_set(const struct adata *clist, const struct f_tree *set)
{
if (!clist)
return 0;
struct f_val v;
if (!clist_set_type(set, &v))
return F_CMP_ERROR;
u32 *l = (u32 *) clist->data;
u32 *end = l + clist->length/4;
while (l < end) {
v.val.i = *l++;
if (find_tree(set, &v))
return 1;
}
return 0;
}
int
eclist_match_set(const struct adata *list, const struct f_tree *set)
{
if (!list)
return 0;
if (!eclist_set_type(set))
return F_CMP_ERROR;
struct f_val v;
u32 *l = int_set_get_data(list);
int len = int_set_get_size(list);
int i;
v.type = T_EC;
for (i = 0; i < len; i += 2) {
v.val.ec = ec_get(l, i);
if (find_tree(set, &v))
return 1;
}
return 0;
}
int
lclist_match_set(const struct adata *list, const struct f_tree *set)
{
if (!list)
return 0;
if (!lclist_set_type(set))
return F_CMP_ERROR;
struct f_val v;
u32 *l = int_set_get_data(list);
int len = int_set_get_size(list);
int i;
v.type = T_LC;
for (i = 0; i < len; i += 3) {
v.val.lc = lc_get(l, i);
if (find_tree(set, &v))
return 1;
}
return 0;
}
const struct adata *
clist_filter(struct linpool *pool, const struct adata *list, const struct f_val *set, int pos)
{
if (!list)
return NULL;
int tree = (set->type == T_SET); /* 1 -> set is T_SET, 0 -> set is T_CLIST */
struct f_val v;
if (tree)
clist_set_type(set->val.t, &v);
else
v.type = T_PAIR;
int len = int_set_get_size(list);
u32 *l = int_set_get_data(list);
u32 tmp[len];
u32 *k = tmp;
u32 *end = l + len;
while (l < end) {
v.val.i = *l++;
/* pos && member(val, set) || !pos && !member(val, set), member() depends on tree */
if ((tree ? !!find_tree(set->val.t, &v) : int_set_contains(set->val.ad, v.val.i)) == pos)
*k++ = v.val.i;
}
uint nl = (k - tmp) * sizeof(u32);
if (nl == list->length)
return list;
struct adata *res = adata_empty(pool, nl);
memcpy(res->data, tmp, nl);
return res;
}
const struct adata *
eclist_filter(struct linpool *pool, const struct adata *list, const struct f_val *set, int pos)
{
if (!list)
return NULL;
int tree = (set->type == T_SET); /* 1 -> set is T_SET, 0 -> set is T_CLIST */
struct f_val v;
int len = int_set_get_size(list);
u32 *l = int_set_get_data(list);
u32 tmp[len];
u32 *k = tmp;
int i;
v.type = T_EC;
for (i = 0; i < len; i += 2) {
v.val.ec = ec_get(l, i);
/* pos && member(val, set) || !pos && !member(val, set), member() depends on tree */
if ((tree ? !!find_tree(set->val.t, &v) : ec_set_contains(set->val.ad, v.val.ec)) == pos) {
*k++ = l[i];
*k++ = l[i+1];
}
}
uint nl = (k - tmp) * sizeof(u32);
if (nl == list->length)
return list;
struct adata *res = adata_empty(pool, nl);
memcpy(res->data, tmp, nl);
return res;
}
const struct adata *
lclist_filter(struct linpool *pool, const struct adata *list, const struct f_val *set, int pos)
{
if (!list)
return NULL;
int tree = (set->type == T_SET); /* 1 -> set is T_SET, 0 -> set is T_CLIST */
struct f_val v;
int len = int_set_get_size(list);
u32 *l = int_set_get_data(list);
u32 tmp[len];
u32 *k = tmp;
int i;
v.type = T_LC;
for (i = 0; i < len; i += 3) {
v.val.lc = lc_get(l, i);
/* pos && member(val, set) || !pos && !member(val, set), member() depends on tree */
if ((tree ? !!find_tree(set->val.t, &v) : lc_set_contains(set->val.ad, v.val.lc)) == pos)
k = lc_copy(k, l+i);
}
uint nl = (k - tmp) * sizeof(u32);
if (nl == list->length)
return list;
struct adata *res = adata_empty(pool, nl);
memcpy(res->data, tmp, nl);
return res;
}
/**
* val_in_range - implement |~| operator
* @v1: element
* @v2: set
*
* Checks if @v1 is element (|~| operator) of @v2.
*/
int
val_in_range(const struct f_val *v1, const struct f_val *v2)
{
if ((v1->type == T_PATH) && (v2->type == T_PATH_MASK))
return as_path_match(v1->val.ad, v2->val.path_mask);
if ((v1->type == T_INT) && (v2->type == T_PATH))
return as_path_contains(v2->val.ad, v1->val.i, 1);
if (((v1->type == T_PAIR) || (v1->type == T_QUAD)) && (v2->type == T_CLIST))
return int_set_contains(v2->val.ad, v1->val.i);
/* IP->Quad implicit conversion */
if (val_is_ip4(v1) && (v2->type == T_CLIST))
return int_set_contains(v2->val.ad, ipa_to_u32(v1->val.ip));
if ((v1->type == T_EC) && (v2->type == T_ECLIST))
return ec_set_contains(v2->val.ad, v1->val.ec);
if ((v1->type == T_LC) && (v2->type == T_LCLIST))
return lc_set_contains(v2->val.ad, v1->val.lc);
if ((v1->type == T_STRING) && (v2->type == T_STRING))
return patmatch(v2->val.s, v1->val.s);
if ((v1->type == T_IP) && (v2->type == T_NET))
return ipa_in_netX(v1->val.ip, v2->val.net);
if ((v1->type == T_NET) && (v2->type == T_NET))
return net_in_netX(v1->val.net, v2->val.net);
if ((v1->type == T_NET) && (v2->type == T_PREFIX_SET))
return trie_match_net(v2->val.ti, v1->val.net);
if (v2->type != T_SET)
return F_CMP_ERROR;
if (!v2->val.t)
return 0;
/* With integrated Quad<->IP implicit conversion */
if ((v1->type == v2->val.t->from.type) ||
((v1->type == T_QUAD) && val_is_ip4(&(v2->val.t->from)) && val_is_ip4(&(v2->val.t->to))))
return !!find_tree(v2->val.t, v1);
if (v1->type == T_CLIST)
return clist_match_set(v1->val.ad, v2->val.t);
if (v1->type == T_ECLIST)
return eclist_match_set(v1->val.ad, v2->val.t);
if (v1->type == T_LCLIST)
return lclist_match_set(v1->val.ad, v2->val.t);
if (v1->type == T_PATH)
return as_path_match_set(v1->val.ad, v2->val.t);
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 rte *rte, buffer *buf)
{
buffer_print(buf, "Block of routes:");
int i = 0;
while (rte)
{
buffer_print(buf, "%s%d: ", i ? "; " : " ", i);
rte_format(rte, buf);
rte = rte->next;
i++;
}
}
/*
* val_format - format filter value
*/
void
val_format(const struct f_val *v, buffer *buf)
{
char buf2[1024];
switch (v->type)
{
case T_VOID: buffer_puts(buf, "(void)"); return;
case T_BOOL: buffer_puts(buf, v->val.i ? "TRUE" : "FALSE"); return;
case T_INT: buffer_print(buf, "%u", v->val.i); return;
case T_STRING: buffer_print(buf, "%s", v->val.s); return;
case T_BYTESTRING: bstrbintohex(v->val.bs->data, v->val.bs->length, buf2, 1000, ':'); buffer_print(buf, "%s", buf2); return;
case T_IP: buffer_print(buf, "%I", v->val.ip); return;
case T_NET: buffer_print(buf, "%N", v->val.net); return;
case T_PAIR: buffer_print(buf, "(%u,%u)", v->val.i >> 16, v->val.i & 0xffff); return;
case T_QUAD: buffer_print(buf, "%R", v->val.i); return;
case T_EC: ec_format(buf2, v->val.ec); buffer_print(buf, "%s", buf2); return;
case T_LC: lc_format(buf2, v->val.lc); buffer_print(buf, "%s", buf2); return;
case T_RD: rd_format(v->val.ec, buf2, 1024); buffer_print(buf, "%s", buf2); return;
case T_PREFIX_SET: trie_format(v->val.ti, buf); return;
case T_SET: tree_format(v->val.t, buf); return;
case T_ENUM: buffer_print(buf, "(enum %x)%u", v->type, v->val.i); return;
case T_PATH: as_path_format(v->val.ad, buf2, 1000); buffer_print(buf, "(path %s)", buf2); return;
case T_CLIST: int_set_format(v->val.ad, 1, -1, buf2, 1000); buffer_print(buf, "(clist %s)", buf2); return;
case T_ECLIST: ec_set_format(v->val.ad, -1, buf2, 1000); buffer_print(buf, "(eclist %s)", buf2); return;
case T_LCLIST: lc_set_format(v->val.ad, -1, buf2, 1000); buffer_print(buf, "(lclist %s)", buf2); return;
case T_PATH_MASK: pm_format(v->val.path_mask, buf); return;
case T_ROUTE: rte_format(v->val.rte, buf); return;
case T_ROUTES_BLOCK: rte_block_format(v->val.rte, buf); return;
default: buffer_print(buf, "[unknown type %x]", v->type); return;
}
}
char *
val_format_str(struct linpool *lp, const struct f_val *v) {
buffer b;
LOG_BUFFER_INIT(b);
val_format(v, &b);
return lp_strdup(lp, b.start);
}
static char val_dump_buffer[1024];
const char *
val_dump(const struct f_val *v) {
static buffer b = {
.start = val_dump_buffer,
.end = val_dump_buffer + 1024,
};
b.pos = b.start;
val_format(v, &b);
return val_dump_buffer;
}