mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2024-12-22 17:51:53 +00:00
Nest: Implement BGP path mask loop operator
Implement regex-like '+' operator in BGP path masks to match previous path mask item multiple times. This is useful as ASNs may appear multiple times in paths due to path prepending for traffic engineering purposes.
This commit is contained in:
parent
5fc8407177
commit
ec430a7fee
@ -1471,11 +1471,16 @@ in the foot).
|
|||||||
<cf/*/ matches any (even empty) sequence of arbitrary AS numbers and
|
<cf/*/ matches any (even empty) sequence of arbitrary AS numbers and
|
||||||
<cf/?/ matches one arbitrary AS number. For example, if <cf>bgp_path</cf>
|
<cf/?/ matches one arbitrary AS number. For example, if <cf>bgp_path</cf>
|
||||||
is 4 3 2 1, then: <tt>bgp_path ˜ [= * 4 3 * =]</tt> is true,
|
is 4 3 2 1, then: <tt>bgp_path ˜ [= * 4 3 * =]</tt> is true,
|
||||||
but <tt>bgp_path ˜ [= * 4 5 * =]</tt> is false. BGP mask
|
but <tt>bgp_path ˜ [= * 4 5 * =]</tt> is false. There is also
|
||||||
expressions can also contain integer expressions enclosed in parenthesis
|
<cf/+/ operator which matches one or multiple instances of previous
|
||||||
and integer variables, for example <tt>[= * 4 (1+2) a =]</tt>. You can
|
expression, e.g. <tt>[= 1 2+ 3 =]</tt> matches both path 1 2 3 and path
|
||||||
also use ranges (e.g. <tt>[= * 3..5 2 100..200 * =]</tt>) and sets
|
1 2 2 2 3, but not 1 3 nor 1 2 4 3. Note that while <cf/*/ and <cf/?/
|
||||||
(e.g. <tt>[= 1 2 [3, 5, 7] * =]</tt>).
|
are wildcard-style operators, <cf/+/ is regex-style operator.
|
||||||
|
|
||||||
|
BGP mask expressions can also contain integer expressions enclosed in
|
||||||
|
parenthesis and integer variables, for example <tt>[= * 4 (1+2) a =]</tt>.
|
||||||
|
You can also use ranges (e.g. <tt>[= * 3..5 2 100..200 * =]</tt>)
|
||||||
|
and sets (e.g. <tt>[= 1 2 [3, 5, 7] * =]</tt>).
|
||||||
|
|
||||||
<tag><label id="type-clist">clist</tag>
|
<tag><label id="type-clist">clist</tag>
|
||||||
Clist is similar to a set, except that unlike other sets, it can be
|
Clist is similar to a set, except that unlike other sets, it can be
|
||||||
|
@ -662,6 +662,7 @@ bgp_path_tail:
|
|||||||
}
|
}
|
||||||
| '*' bgp_path_tail { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_PATH_MASK_ITEM, .val.pmi = { .kind = PM_ASTERISK }, }); $$->next = $2; }
|
| '*' bgp_path_tail { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_PATH_MASK_ITEM, .val.pmi = { .kind = PM_ASTERISK }, }); $$->next = $2; }
|
||||||
| '?' bgp_path_tail { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_PATH_MASK_ITEM, .val.pmi = { .kind = PM_QUESTION }, }); $$->next = $2; }
|
| '?' bgp_path_tail { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_PATH_MASK_ITEM, .val.pmi = { .kind = PM_QUESTION }, }); $$->next = $2; }
|
||||||
|
| '+' bgp_path_tail { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_PATH_MASK_ITEM, .val.pmi = { .kind = PM_LOOP }, }); $$->next = $2; }
|
||||||
| bgp_path_expr bgp_path_tail { $$ = $1; $$->next = $2; }
|
| bgp_path_expr bgp_path_tail { $$ = $1; $$->next = $2; }
|
||||||
| { $$ = NULL; }
|
| { $$ = NULL; }
|
||||||
;
|
;
|
||||||
|
@ -93,6 +93,8 @@ adata_empty(struct linpool *pool, int l)
|
|||||||
static void
|
static void
|
||||||
pm_format(const struct f_path_mask *p, buffer *buf)
|
pm_format(const struct f_path_mask *p, buffer *buf)
|
||||||
{
|
{
|
||||||
|
int loop = 0;
|
||||||
|
|
||||||
buffer_puts(buf, "[= ");
|
buffer_puts(buf, "[= ");
|
||||||
|
|
||||||
for (uint i=0; i<p->len; i++)
|
for (uint i=0; i<p->len; i++)
|
||||||
@ -111,6 +113,10 @@ pm_format(const struct f_path_mask *p, buffer *buf)
|
|||||||
buffer_puts(buf, "* ");
|
buffer_puts(buf, "* ");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PM_LOOP:
|
||||||
|
loop = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
case PM_ASN_RANGE:
|
case PM_ASN_RANGE:
|
||||||
buffer_print(buf, "%u..%u ", p->item[i].from, p->item[i].to);
|
buffer_print(buf, "%u..%u ", p->item[i].from, p->item[i].to);
|
||||||
break;
|
break;
|
||||||
@ -119,6 +125,11 @@ pm_format(const struct f_path_mask *p, buffer *buf)
|
|||||||
ASSERT(0);
|
ASSERT(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loop && (p->item[i].kind != PM_LOOP))
|
||||||
|
{
|
||||||
|
buffer_puts(buf, "+ ");
|
||||||
|
loop = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_puts(buf, "=]");
|
buffer_puts(buf, "=]");
|
||||||
|
@ -311,6 +311,17 @@
|
|||||||
for (uint i=0; i<whati->varcount; i++) {
|
for (uint i=0; i<whati->varcount; i++) {
|
||||||
switch (vv(i).type) {
|
switch (vv(i).type) {
|
||||||
case T_PATH_MASK_ITEM:
|
case T_PATH_MASK_ITEM:
|
||||||
|
if (vv(i).val.pmi.kind == PM_LOOP)
|
||||||
|
{
|
||||||
|
if (i == 0)
|
||||||
|
runtime("Path mask iterator '+' cannot be first");
|
||||||
|
|
||||||
|
/* We want PM_LOOP as prefix operator */
|
||||||
|
pm->item[i] = pm->item[i - 1];
|
||||||
|
pm->item[i - 1] = vv(i).val.pmi;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
pm->item[i] = vv(i).val.pmi;
|
pm->item[i] = vv(i).val.pmi;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -646,7 +646,6 @@ int set set12;
|
|||||||
bt_assert(delete(p2, 3) = prepend(prepend(prepend(prepend(+empty+, 1), 2), 4), 5));
|
bt_assert(delete(p2, 3) = prepend(prepend(prepend(prepend(+empty+, 1), 2), 4), 5));
|
||||||
bt_assert(filter(p2, [1..3]) = prepend(prepend(prepend(+empty+, 1), 2), 3));
|
bt_assert(filter(p2, [1..3]) = prepend(prepend(prepend(+empty+, 1), 2), 3));
|
||||||
|
|
||||||
pm1 = [= 1 2 * 3 4 5 =];
|
|
||||||
p2 = prepend( + empty +, 5 );
|
p2 = prepend( + empty +, 5 );
|
||||||
p2 = prepend( p2, 4 );
|
p2 = prepend( p2, 4 );
|
||||||
p2 = prepend( p2, 3 );
|
p2 = prepend( p2, 3 );
|
||||||
@ -654,9 +653,17 @@ int set set12;
|
|||||||
p2 = prepend( p2, 2 );
|
p2 = prepend( p2, 2 );
|
||||||
p2 = prepend( p2, 1 );
|
p2 = prepend( p2, 1 );
|
||||||
|
|
||||||
bt_assert(p2 ~ pm1);
|
bt_assert(p2 !~ [= 1 2 3 4 5 =]);
|
||||||
|
bt_assert(p2 ~ [= 1 2 * 4 5 =]);
|
||||||
|
bt_assert(p2 ~ [= 1 2 * 3 4 5 =]);
|
||||||
|
bt_assert(p2 ~ [= 1 2 3+ 4 5 =]);
|
||||||
|
bt_assert(p2 ~ [= 1 2 3+ 4+ 5 =]);
|
||||||
|
bt_assert(p2 !~ [= 1 2 3+ 5+ 4 5 =]);
|
||||||
|
bt_assert(p2 !~ [= 1 2 3 3 5+ 4 5 =]);
|
||||||
bt_assert(delete(p2, 3) = prepend(prepend(prepend(prepend(+empty+, 5), 4), 2), 1));
|
bt_assert(delete(p2, 3) = prepend(prepend(prepend(prepend(+empty+, 5), 4), 2), 1));
|
||||||
bt_assert(delete(p2, [4..5]) = prepend(prepend(prepend(prepend(+empty+, 3), 3), 2), 1));
|
bt_assert(delete(p2, [4..5]) = prepend(prepend(prepend(prepend(+empty+, 3), 3), 2), 1));
|
||||||
|
|
||||||
|
bt_assert(format([= 1 2+ 3 =]) = "[= 1 2 + 3 =]");
|
||||||
}
|
}
|
||||||
|
|
||||||
bt_test_suite(t_path, "Testing paths");
|
bt_test_suite(t_path, "Testing paths");
|
||||||
|
@ -727,7 +727,7 @@ parse_path(const struct adata *path, struct pm_pos *pp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
pm_match(struct pm_pos *pos, u32 asn, u32 asn2)
|
pm_match_val(const struct pm_pos *pos, u32 asn, u32 asn2)
|
||||||
{
|
{
|
||||||
u32 gas;
|
u32 gas;
|
||||||
if (! pos->set)
|
if (! pos->set)
|
||||||
@ -749,7 +749,7 @@ pm_match(struct pm_pos *pos, u32 asn, u32 asn2)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
pm_match_set(struct pm_pos *pos, const struct f_tree *set)
|
pm_match_set(const struct pm_pos *pos, const struct f_tree *set)
|
||||||
{
|
{
|
||||||
struct f_val asn = { .type = T_INT };
|
struct f_val asn = { .type = T_INT };
|
||||||
|
|
||||||
@ -773,25 +773,34 @@ pm_match_set(struct pm_pos *pos, const struct f_tree *set)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static inline int
|
||||||
pm_mark(struct pm_pos *pos, int i, int plen, int *nl, int *nh)
|
pm_match(const struct pm_pos *pos, const struct f_path_mask_item *mask, u32 asn, u32 asn2)
|
||||||
{
|
{
|
||||||
int j;
|
return ((mask->kind == PM_QUESTION) ||
|
||||||
|
((mask->kind != PM_ASN_SET) ?
|
||||||
|
pm_match_val(pos, asn, asn2) :
|
||||||
|
pm_match_set(pos, mask->set)));
|
||||||
|
}
|
||||||
|
|
||||||
if (pos[i].set)
|
static void
|
||||||
pos[i].mark = 1;
|
pm_mark(struct pm_pos *pos, int *i, int plen, int *nl, int *nh)
|
||||||
|
{
|
||||||
|
int j = *i;
|
||||||
|
|
||||||
|
if (pos[j].set)
|
||||||
|
do { pos[j].mark = 1; j++; }
|
||||||
|
while ((j < plen) && pos[j].set);
|
||||||
|
else
|
||||||
|
j++;
|
||||||
|
|
||||||
for (j = i + 1; (j < plen) && pos[j].set && (! pos[j].mark); j++)
|
|
||||||
pos[j].mark = 1;
|
|
||||||
pos[j].mark = 1;
|
pos[j].mark = 1;
|
||||||
|
|
||||||
/* We are going downwards, therefore every mark is
|
/* Update low, high based on first and last marked pos */
|
||||||
new low and just the first mark is new high */
|
int l = pos[*i].set ? *i : j;
|
||||||
|
|
||||||
*nl = i + (pos[i].set ? 0 : 1);
|
*nl = (*nl < 0) ? l : MIN(*nl, l);
|
||||||
|
*nh = MAX(*nh, j);
|
||||||
if (*nh < 0)
|
*i = j;
|
||||||
*nh = j;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* AS path matching is nontrivial. Because AS path can
|
/* AS path matching is nontrivial. Because AS path can
|
||||||
@ -821,7 +830,7 @@ as_path_match(const struct adata *path, const struct f_path_mask *mask)
|
|||||||
{
|
{
|
||||||
struct pm_pos pos[2048 + 1];
|
struct pm_pos pos[2048 + 1];
|
||||||
int plen = parse_path(path, pos);
|
int plen = parse_path(path, pos);
|
||||||
int l, h, i, nh, nl;
|
int l, h, i, nh, nl, last, loop;
|
||||||
u32 val = 0;
|
u32 val = 0;
|
||||||
u32 val2 = 0;
|
u32 val2 = 0;
|
||||||
|
|
||||||
@ -831,7 +840,7 @@ as_path_match(const struct adata *path, const struct f_path_mask *mask)
|
|||||||
pos[plen].set = 0;
|
pos[plen].set = 0;
|
||||||
pos[plen].mark = 0;
|
pos[plen].mark = 0;
|
||||||
|
|
||||||
l = h = 0;
|
l = h = loop = 0;
|
||||||
pos[0].mark = 1;
|
pos[0].mark = 1;
|
||||||
|
|
||||||
for (uint m=0; m < mask->len; m++)
|
for (uint m=0; m < mask->len; m++)
|
||||||
@ -847,6 +856,10 @@ as_path_match(const struct adata *path, const struct f_path_mask *mask)
|
|||||||
h = plen;
|
h = plen;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PM_LOOP:
|
||||||
|
loop = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
case PM_ASN: /* Define single ASN as ASN..ASN - very narrow interval */
|
case PM_ASN: /* Define single ASN as ASN..ASN - very narrow interval */
|
||||||
val2 = val = mask->item[m].asn;
|
val2 = val = mask->item[m].asn;
|
||||||
goto step;
|
goto step;
|
||||||
@ -860,15 +873,22 @@ as_path_match(const struct adata *path, const struct f_path_mask *mask)
|
|||||||
case PM_ASN_SET:
|
case PM_ASN_SET:
|
||||||
step:
|
step:
|
||||||
nh = nl = -1;
|
nh = nl = -1;
|
||||||
|
last = plen;
|
||||||
for (i = h; i >= l; i--)
|
for (i = h; i >= l; i--)
|
||||||
if (pos[i].mark)
|
if (pos[i].mark)
|
||||||
{
|
{
|
||||||
pos[i].mark = 0;
|
pos[i].mark = 0;
|
||||||
if ((mask->item[m].kind == PM_QUESTION) ||
|
int j = i;
|
||||||
((mask->item[m].kind != PM_ASN_SET) ?
|
|
||||||
pm_match(pos + i, val, val2) :
|
match:
|
||||||
pm_match_set(pos + i, mask->item[m].set)))
|
if (pm_match(pos + j, &mask->item[m], val, val2))
|
||||||
pm_mark(pos, i, plen, &nl, &nh);
|
{
|
||||||
|
pm_mark(pos, &j, plen, &nl, &nh);
|
||||||
|
if (loop && (j < last))
|
||||||
|
goto match;
|
||||||
|
}
|
||||||
|
|
||||||
|
last = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nh < 0)
|
if (nh < 0)
|
||||||
@ -876,6 +896,7 @@ as_path_match(const struct adata *path, const struct f_path_mask *mask)
|
|||||||
|
|
||||||
h = nh;
|
h = nh;
|
||||||
l = nl;
|
l = nl;
|
||||||
|
loop = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,7 @@ static inline struct adata *as_path_prepend(struct linpool *pool, const struct a
|
|||||||
#define PM_ASN_EXPR 3
|
#define PM_ASN_EXPR 3
|
||||||
#define PM_ASN_RANGE 4
|
#define PM_ASN_RANGE 4
|
||||||
#define PM_ASN_SET 5
|
#define PM_ASN_SET 5
|
||||||
|
#define PM_LOOP 6
|
||||||
|
|
||||||
struct f_path_mask_item {
|
struct f_path_mask_item {
|
||||||
union {
|
union {
|
||||||
|
Loading…
Reference in New Issue
Block a user