From: Ondrej Zajicek (work) Date: Mon, 18 May 2020 14:25:08 +0000 (+0200) Subject: Nest: Implement BGP path mask loop operator X-Git-Tag: v2.0.8~70 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ec430a7feefd3b32ee39c641a48c44528d0eab25;p=thirdparty%2Fbird.git 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. --- diff --git a/doc/bird.sgml b/doc/bird.sgml index 1808d04cb..786f124b7 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -1471,11 +1471,16 @@ in the foot). bgp_path is 4 3 2 1, then: bgp_path ˜ [= * 4 3 * =] is true, - but bgp_path ˜ [= * 4 5 * =] is false. BGP mask - expressions can also contain integer expressions enclosed in parenthesis - and integer variables, for example [= * 4 (1+2) a =]. You can - also use ranges (e.g. [= * 3..5 2 100..200 * =]) and sets - (e.g. [= 1 2 [3, 5, 7] * =]). + but bgp_path ˜ [= * 4 5 * =] is false. There is also + [= 1 2+ 3 =] matches both path 1 2 3 and path + 1 2 2 2 3, but not 1 3 nor 1 2 4 3. Note that while [= * 4 (1+2) a =]. + You can also use ranges (e.g. [= * 3..5 2 100..200 * =]) + and sets (e.g. [= 1 2 [3, 5, 7] * =]). Clist is similar to a set, except that unlike other sets, it can be diff --git a/filter/config.Y b/filter/config.Y index 77424a8b2..557a951f0 100644 --- a/filter/config.Y +++ b/filter/config.Y @@ -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_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; } | { $$ = NULL; } ; diff --git a/filter/data.c b/filter/data.c index 62edd9e0e..402202554 100644 --- a/filter/data.c +++ b/filter/data.c @@ -93,6 +93,8 @@ adata_empty(struct linpool *pool, int l) static void pm_format(const struct f_path_mask *p, buffer *buf) { + int loop = 0; + buffer_puts(buf, "[= "); for (uint i=0; ilen; i++) @@ -111,6 +113,10 @@ pm_format(const struct f_path_mask *p, buffer *buf) 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; @@ -119,6 +125,11 @@ pm_format(const struct f_path_mask *p, buffer *buf) ASSERT(0); } + if (loop && (p->item[i].kind != PM_LOOP)) + { + buffer_puts(buf, "+ "); + loop = 0; + } } buffer_puts(buf, "=]"); diff --git a/filter/f-inst.c b/filter/f-inst.c index 63a6bdab2..df908e26e 100644 --- a/filter/f-inst.c +++ b/filter/f-inst.c @@ -311,6 +311,17 @@ for (uint i=0; ivarcount; i++) { switch (vv(i).type) { 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; break; diff --git a/filter/test.conf b/filter/test.conf index 634816fd9..63af25bb4 100644 --- a/filter/test.conf +++ b/filter/test.conf @@ -646,7 +646,6 @@ int set set12; 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)); - pm1 = [= 1 2 * 3 4 5 =]; p2 = prepend( + empty +, 5 ); p2 = prepend( p2, 4 ); p2 = prepend( p2, 3 ); @@ -654,9 +653,17 @@ int set set12; p2 = prepend( p2, 2 ); 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, [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"); diff --git a/nest/a-path.c b/nest/a-path.c index cffd46ab2..2e34a3d1c 100644 --- a/nest/a-path.c +++ b/nest/a-path.c @@ -727,7 +727,7 @@ parse_path(const struct adata *path, struct pm_pos *pp) } 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; if (! pos->set) @@ -749,7 +749,7 @@ pm_match(struct pm_pos *pos, u32 asn, u32 asn2) } 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 }; @@ -773,25 +773,34 @@ pm_match_set(struct pm_pos *pos, const struct f_tree *set) return 0; } +static inline int +pm_match(const struct pm_pos *pos, const struct f_path_mask_item *mask, u32 asn, u32 asn2) +{ + return ((mask->kind == PM_QUESTION) || + ((mask->kind != PM_ASN_SET) ? + pm_match_val(pos, asn, asn2) : + pm_match_set(pos, mask->set))); +} + static void -pm_mark(struct pm_pos *pos, int i, int plen, int *nl, int *nh) +pm_mark(struct pm_pos *pos, int *i, int plen, int *nl, int *nh) { - int j; + int j = *i; - if (pos[i].set) - pos[i].mark = 1; + 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; - /* We are going downwards, therefore every mark is - new low and just the first mark is new high */ - - *nl = i + (pos[i].set ? 0 : 1); + /* Update low, high based on first and last marked pos */ + int l = pos[*i].set ? *i : j; - if (*nh < 0) - *nh = j; + *nl = (*nl < 0) ? l : MIN(*nl, l); + *nh = MAX(*nh, j); + *i = j; } /* 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]; int plen = parse_path(path, pos); - int l, h, i, nh, nl; + int l, h, i, nh, nl, last, loop; u32 val = 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].mark = 0; - l = h = 0; + l = h = loop = 0; pos[0].mark = 1; 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; break; + case PM_LOOP: + loop = 1; + break; + case PM_ASN: /* Define single ASN as ASN..ASN - very narrow interval */ val2 = val = mask->item[m].asn; goto step; @@ -860,15 +873,22 @@ as_path_match(const struct adata *path, const struct f_path_mask *mask) case PM_ASN_SET: step: nh = nl = -1; + last = plen; for (i = h; i >= l; i--) if (pos[i].mark) { pos[i].mark = 0; - if ((mask->item[m].kind == PM_QUESTION) || - ((mask->item[m].kind != PM_ASN_SET) ? - pm_match(pos + i, val, val2) : - pm_match_set(pos + i, mask->item[m].set))) - pm_mark(pos, i, plen, &nl, &nh); + int j = i; + + match: + if (pm_match(pos + j, &mask->item[m], val, val2)) + { + pm_mark(pos, &j, plen, &nl, &nh); + if (loop && (j < last)) + goto match; + } + + last = i; } if (nh < 0) @@ -876,6 +896,7 @@ as_path_match(const struct adata *path, const struct f_path_mask *mask) h = nh; l = nl; + loop = 0; break; } } diff --git a/nest/attrs.h b/nest/attrs.h index d88a73f34..50da817be 100644 --- a/nest/attrs.h +++ b/nest/attrs.h @@ -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_RANGE 4 #define PM_ASN_SET 5 +#define PM_LOOP 6 struct f_path_mask_item { union {