mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2024-11-18 17:18:42 +00:00
ec430a7fee
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.
922 lines
26 KiB
Plaintext
922 lines
26 KiB
Plaintext
/*
|
|
* BIRD - filters
|
|
*
|
|
* Copyright 1998--2000 Pavel Machek
|
|
*
|
|
* Can be freely distributed and used under the terms of the GNU GPL.
|
|
*
|
|
FIXME: priority of ! should be lower
|
|
*/
|
|
|
|
CF_HDR
|
|
|
|
#include "filter/f-inst.h"
|
|
#include "filter/data.h"
|
|
|
|
CF_DEFINES
|
|
|
|
static inline u32 pair(u32 a, u32 b) { return (a << 16) | b; }
|
|
static inline u32 pair_a(u32 p) { return p >> 16; }
|
|
static inline u32 pair_b(u32 p) { return p & 0xFFFF; }
|
|
|
|
#define f_generate_complex(fi_code, da, arg) \
|
|
f_new_inst(FI_EA_SET, f_new_inst(fi_code, f_new_inst(FI_EA_GET, da), arg), da)
|
|
|
|
/*
|
|
* Sets and their items are during parsing handled as lists, linked
|
|
* through left ptr. The first item in a list also contains a pointer
|
|
* to the last item in a list (right ptr). For convenience, even items
|
|
* are handled as one-item lists. Lists are merged by f_merge_items().
|
|
*/
|
|
static int
|
|
f_valid_set_type(int type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case T_INT:
|
|
case T_PAIR:
|
|
case T_QUAD:
|
|
case T_ENUM:
|
|
case T_IP:
|
|
case T_EC:
|
|
case T_LC:
|
|
case T_RD:
|
|
return 1;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static inline struct f_tree *
|
|
f_new_item(struct f_val from, struct f_val to)
|
|
{
|
|
struct f_tree *t = f_new_tree();
|
|
t->right = t;
|
|
t->from = from;
|
|
t->to = to;
|
|
return t;
|
|
}
|
|
|
|
static inline struct f_tree *
|
|
f_merge_items(struct f_tree *a, struct f_tree *b)
|
|
{
|
|
if (!a) return b;
|
|
a->right->left = b;
|
|
a->right = b->right;
|
|
b->right = NULL;
|
|
return a;
|
|
}
|
|
|
|
static inline struct f_tree *
|
|
f_new_pair_item(int fa, int ta, int fb, int tb)
|
|
{
|
|
check_u16(fa);
|
|
check_u16(ta);
|
|
check_u16(fb);
|
|
check_u16(tb);
|
|
|
|
if ((ta < fa) || (tb < fb))
|
|
cf_error( "From value cannot be higher that To value in pair sets");
|
|
|
|
struct f_tree *t = f_new_tree();
|
|
t->right = t;
|
|
t->from.type = t->to.type = T_PAIR;
|
|
t->from.val.i = pair(fa, fb);
|
|
t->to.val.i = pair(ta, tb);
|
|
return t;
|
|
}
|
|
|
|
static inline struct f_tree *
|
|
f_new_pair_set(int fa, int ta, int fb, int tb)
|
|
{
|
|
check_u16(fa);
|
|
check_u16(ta);
|
|
check_u16(fb);
|
|
check_u16(tb);
|
|
|
|
if ((ta < fa) || (tb < fb))
|
|
cf_error( "From value cannot be higher that To value in pair sets");
|
|
|
|
struct f_tree *lst = NULL;
|
|
int i;
|
|
|
|
for (i = fa; i <= ta; i++)
|
|
lst = f_merge_items(lst, f_new_pair_item(i, i, fb, tb));
|
|
|
|
return lst;
|
|
}
|
|
|
|
#define CC_ALL 0xFFFF
|
|
#define EC_ALL 0xFFFFFFFF
|
|
#define LC_ALL 0xFFFFFFFF
|
|
|
|
static struct f_tree *
|
|
f_new_ec_item(u32 kind, u32 ipv4_used, u32 key, u32 vf, u32 vt)
|
|
{
|
|
u64 fm, to;
|
|
|
|
if ((kind != EC_GENERIC) && (ipv4_used || (key >= 0x10000))) {
|
|
check_u16(vf);
|
|
if (vt == EC_ALL)
|
|
vt = 0xFFFF;
|
|
else
|
|
check_u16(vt);
|
|
}
|
|
|
|
if (kind == EC_GENERIC) {
|
|
fm = ec_generic(key, vf);
|
|
to = ec_generic(key, vt);
|
|
}
|
|
else if (ipv4_used) {
|
|
fm = ec_ip4(kind, key, vf);
|
|
to = ec_ip4(kind, key, vt);
|
|
}
|
|
else if (key < 0x10000) {
|
|
fm = ec_as2(kind, key, vf);
|
|
to = ec_as2(kind, key, vt);
|
|
}
|
|
else {
|
|
fm = ec_as4(kind, key, vf);
|
|
to = ec_as4(kind, key, vt);
|
|
}
|
|
|
|
struct f_tree *t = f_new_tree();
|
|
t->right = t;
|
|
t->from.type = t->to.type = T_EC;
|
|
t->from.val.ec = fm;
|
|
t->to.val.ec = to;
|
|
return t;
|
|
}
|
|
|
|
static struct f_tree *
|
|
f_new_lc_item(u32 f1, u32 t1, u32 f2, u32 t2, u32 f3, u32 t3)
|
|
{
|
|
struct f_tree *t = f_new_tree();
|
|
t->right = t;
|
|
t->from.type = t->to.type = T_LC;
|
|
t->from.val.lc = (lcomm) {f1, f2, f3};
|
|
t->to.val.lc = (lcomm) {t1, t2, t3};
|
|
return t;
|
|
}
|
|
|
|
static inline struct f_inst *
|
|
f_generate_empty(struct f_dynamic_attr dyn)
|
|
{
|
|
struct f_val empty;
|
|
|
|
switch (dyn.type & EAF_TYPE_MASK) {
|
|
case EAF_TYPE_AS_PATH:
|
|
empty = f_const_empty_path;
|
|
break;
|
|
case EAF_TYPE_INT_SET:
|
|
empty = f_const_empty_clist;
|
|
break;
|
|
case EAF_TYPE_EC_SET:
|
|
empty = f_const_empty_eclist;
|
|
break;
|
|
case EAF_TYPE_LC_SET:
|
|
empty = f_const_empty_lclist;
|
|
break;
|
|
default:
|
|
cf_error("Can't empty that attribute");
|
|
}
|
|
|
|
return f_new_inst(FI_EA_SET, f_new_inst(FI_CONSTANT, empty), dyn);
|
|
}
|
|
|
|
/*
|
|
* Remove all new lines and doubled whitespaces
|
|
* and convert all tabulators to spaces
|
|
* and return a copy of string
|
|
*/
|
|
char *
|
|
assert_copy_expr(const char *start, size_t len)
|
|
{
|
|
/* XXX: Allocates maybe a little more memory than we really finally need */
|
|
char *str = cfg_alloc(len + 1);
|
|
|
|
char *dst = str;
|
|
const char *src = start - 1;
|
|
const char *end = start + len;
|
|
while (++src < end)
|
|
{
|
|
if (*src == '\n')
|
|
continue;
|
|
|
|
/* Skip doubled whitespaces */
|
|
if (src != start)
|
|
{
|
|
const char *prev = src - 1;
|
|
if ((*src == ' ' || *src == '\t') && (*prev == ' ' || *prev == '\t'))
|
|
continue;
|
|
}
|
|
|
|
if (*src == '\t')
|
|
*dst = ' ';
|
|
else
|
|
*dst = *src;
|
|
|
|
dst++;
|
|
}
|
|
*dst = '\0';
|
|
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* assert_done - create f_instruction of bt_assert
|
|
* @expr: expression in bt_assert()
|
|
* @start: pointer to first char of test expression
|
|
* @end: pointer to the last char of test expression
|
|
*/
|
|
static struct f_inst *
|
|
assert_done(struct f_inst *expr, const char *start, const char *end)
|
|
{
|
|
return f_new_inst(FI_ASSERT, expr,
|
|
(end >= start) ?
|
|
assert_copy_expr(start, end - start + 1)
|
|
: "???");
|
|
}
|
|
|
|
static struct f_inst *
|
|
assert_assign(struct f_lval *lval, struct f_inst *expr, const char *start, const char *end)
|
|
{
|
|
struct f_inst *setter, *getter, *checker;
|
|
switch (lval->type) {
|
|
case F_LVAL_VARIABLE:
|
|
setter = f_new_inst(FI_VAR_SET, expr, lval->sym);
|
|
getter = f_new_inst(FI_VAR_GET, lval->sym);
|
|
break;
|
|
case F_LVAL_PREFERENCE:
|
|
setter = f_new_inst(FI_PREF_SET, expr);
|
|
getter = f_new_inst(FI_PREF_GET);
|
|
break;
|
|
case F_LVAL_SA:
|
|
setter = f_new_inst(FI_RTA_SET, expr, lval->sa);
|
|
getter = f_new_inst(FI_RTA_GET, lval->sa);
|
|
break;
|
|
case F_LVAL_EA:
|
|
setter = f_new_inst(FI_EA_SET, expr, lval->da);
|
|
getter = f_new_inst(FI_EA_GET, lval->da);
|
|
break;
|
|
default:
|
|
bug("Unknown lval type");
|
|
}
|
|
|
|
checker = f_new_inst(FI_EQ, expr, getter);
|
|
setter->next = checker;
|
|
|
|
return assert_done(setter, start, end);
|
|
}
|
|
|
|
CF_DECLS
|
|
|
|
CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
|
|
ACCEPT, REJECT, ERROR,
|
|
INT, BOOL, IP, TYPE, PREFIX, RD, PAIR, QUAD, EC, LC,
|
|
SET, STRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST,
|
|
IF, THEN, ELSE, CASE,
|
|
TRUE, FALSE, RT, RO, UNKNOWN, GENERIC,
|
|
FROM, GW, NET, MASK, PROTO, SOURCE, SCOPE, DEST, IFNAME, IFINDEX,
|
|
PREFERENCE,
|
|
ROA_CHECK, ASN, SRC, DST,
|
|
IS_V4, IS_V6,
|
|
LEN, MAXLEN,
|
|
DEFINED,
|
|
ADD, DELETE, CONTAINS, RESET,
|
|
PREPEND, FIRST, LAST, LAST_NONAGGREGATED, MATCH,
|
|
EMPTY,
|
|
FILTER, WHERE, EVAL, ATTRIBUTE,
|
|
BT_ASSERT, BT_TEST_SUITE, BT_CHECK_ASSIGN, BT_TEST_SAME, FORMAT)
|
|
|
|
%nonassoc THEN
|
|
%nonassoc ELSE
|
|
|
|
%type <xp> cmds_int cmd_prep
|
|
%type <x> term block cmd cmds constant constructor print_list var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail
|
|
%type <fda> dynamic_attr
|
|
%type <fsa> static_attr
|
|
%type <f> filter where_filter
|
|
%type <fl> filter_body function_body
|
|
%type <flv> lvalue
|
|
%type <i> type function_args function_vars
|
|
%type <ecs> ec_kind
|
|
%type <fret> break_command
|
|
%type <i32> cnum
|
|
%type <e> pair_item ec_item lc_item set_item switch_item set_items switch_items switch_body
|
|
%type <trie> fprefix_set
|
|
%type <v> set_atom switch_atom fipa
|
|
%type <px> fprefix
|
|
%type <t> get_cf_position
|
|
|
|
CF_GRAMMAR
|
|
|
|
conf: filter_def ;
|
|
filter_def:
|
|
FILTER symbol { $2 = cf_define_symbol($2, SYM_FILTER, filter, NULL); cf_push_scope( $2 ); }
|
|
filter_body {
|
|
struct filter *f = cfg_alloc(sizeof(struct filter));
|
|
*f = (struct filter) { .sym = $2, .root = $4 };
|
|
$2->filter = f;
|
|
|
|
cf_pop_scope();
|
|
}
|
|
;
|
|
|
|
conf: filter_eval ;
|
|
filter_eval:
|
|
EVAL term { f_eval_int(f_linearize($2)); }
|
|
;
|
|
|
|
conf: custom_attr ;
|
|
custom_attr: ATTRIBUTE type symbol ';' {
|
|
cf_define_symbol($3, SYM_ATTRIBUTE, attribute, ca_lookup(new_config->pool, $3->name, $2)->fda);
|
|
};
|
|
|
|
conf: bt_test_suite ;
|
|
bt_test_suite:
|
|
BT_TEST_SUITE '(' CF_SYM_KNOWN ',' text ')' {
|
|
cf_assert_symbol($3, SYM_FUNCTION);
|
|
struct f_bt_test_suite *t = cfg_allocz(sizeof(struct f_bt_test_suite));
|
|
t->fn = $3->function;
|
|
t->fn_name = $3->name;
|
|
t->dsc = $5;
|
|
|
|
add_tail(&new_config->tests, &t->n);
|
|
}
|
|
;
|
|
|
|
conf: bt_test_same ;
|
|
bt_test_same:
|
|
BT_TEST_SAME '(' CF_SYM_KNOWN ',' CF_SYM_KNOWN ',' NUM ')' {
|
|
cf_assert_symbol($3, SYM_FUNCTION);
|
|
cf_assert_symbol($5, SYM_FUNCTION);
|
|
struct f_bt_test_suite *t = cfg_allocz(sizeof(struct f_bt_test_suite));
|
|
t->fn = $3->function;
|
|
t->cmp = $5->function;
|
|
t->result = $7;
|
|
t->fn_name = $3->name;
|
|
t->dsc = $5->name;
|
|
add_tail(&new_config->tests, &t->n);
|
|
}
|
|
;
|
|
|
|
type:
|
|
INT { $$ = T_INT; }
|
|
| BOOL { $$ = T_BOOL; }
|
|
| IP { $$ = T_IP; }
|
|
| RD { $$ = T_RD; }
|
|
| PREFIX { $$ = T_NET; }
|
|
| PAIR { $$ = T_PAIR; }
|
|
| QUAD { $$ = T_QUAD; }
|
|
| EC { $$ = T_EC; }
|
|
| LC { $$ = T_LC; }
|
|
| STRING { $$ = T_STRING; }
|
|
| BGPMASK { $$ = T_PATH_MASK; }
|
|
| BGPPATH { $$ = T_PATH; }
|
|
| CLIST { $$ = T_CLIST; }
|
|
| ECLIST { $$ = T_ECLIST; }
|
|
| LCLIST { $$ = T_LCLIST; }
|
|
| type SET {
|
|
switch ($1) {
|
|
case T_INT:
|
|
case T_PAIR:
|
|
case T_QUAD:
|
|
case T_EC:
|
|
case T_LC:
|
|
case T_RD:
|
|
case T_IP:
|
|
$$ = T_SET;
|
|
break;
|
|
|
|
case T_NET:
|
|
$$ = T_PREFIX_SET;
|
|
break;
|
|
|
|
default:
|
|
cf_error( "You can't create sets of this type." );
|
|
}
|
|
}
|
|
;
|
|
|
|
function_argsn:
|
|
/* EMPTY */
|
|
| function_argsn type symbol ';' {
|
|
if ($3->scope->slots >= 0xfe) cf_error("Too many declarations, at most 255 allowed");
|
|
cf_define_symbol($3, SYM_VARIABLE | $2, offset, $3->scope->slots++);
|
|
}
|
|
;
|
|
|
|
function_args:
|
|
'(' ')' { $$ = 0; }
|
|
| '(' function_argsn type symbol ')' {
|
|
cf_define_symbol($4, SYM_VARIABLE | $3, offset, $4->scope->slots++);
|
|
$$ = $4->scope->slots;
|
|
}
|
|
;
|
|
|
|
function_vars:
|
|
/* EMPTY */ { $$ = 0; }
|
|
| function_vars type symbol ';' {
|
|
cf_define_symbol($3, SYM_VARIABLE | $2, offset, $3->scope->slots++);
|
|
$$ = $1 + 1;
|
|
}
|
|
;
|
|
|
|
filter_body: function_body ;
|
|
|
|
filter:
|
|
CF_SYM_KNOWN {
|
|
cf_assert_symbol($1, SYM_FILTER);
|
|
$$ = $1->filter;
|
|
}
|
|
| filter_body {
|
|
struct filter *f = cfg_alloc(sizeof(struct filter));
|
|
*f = (struct filter) { .root = $1 };
|
|
$$ = f;
|
|
}
|
|
;
|
|
|
|
where_filter:
|
|
WHERE term {
|
|
/* Construct 'IF term THEN { ACCEPT; } ELSE { REJECT; }' */
|
|
$$ = f_new_where($2);
|
|
}
|
|
;
|
|
|
|
function_body:
|
|
function_vars '{' cmds '}' {
|
|
$$ = f_linearize($3);
|
|
$$->vars = $1;
|
|
}
|
|
;
|
|
|
|
conf: function_def ;
|
|
function_def:
|
|
FUNCTION symbol { DBG( "Beginning of function %s\n", $2->name );
|
|
$2 = cf_define_symbol($2, SYM_FUNCTION, function, NULL);
|
|
cf_push_scope($2);
|
|
} function_args function_body {
|
|
DBG("Definition of function %s with %u args and %u local vars.\n", $2->name, $4, $5->vars);
|
|
$5->args = $4;
|
|
$2->function = $5;
|
|
cf_pop_scope();
|
|
}
|
|
;
|
|
|
|
/* Programs */
|
|
|
|
cmds: /* EMPTY */ { $$ = NULL; }
|
|
| cmds_int { $$ = $1.begin; }
|
|
;
|
|
|
|
cmd_prep: cmd {
|
|
$$.begin = $$.end = $1;
|
|
if ($1)
|
|
while ($$.end->next)
|
|
$$.end = $$.end->next;
|
|
}
|
|
;
|
|
|
|
cmds_int: cmd_prep
|
|
| cmds_int cmd_prep {
|
|
if (!$1.begin)
|
|
$$ = $2;
|
|
else if (!$2.begin)
|
|
$$ = $1;
|
|
else {
|
|
$$.begin = $1.begin;
|
|
$$.end = $2.end;
|
|
$1.end->next = $2.begin;
|
|
}
|
|
}
|
|
;
|
|
|
|
block:
|
|
cmd {
|
|
$$=$1;
|
|
}
|
|
| '{' cmds '}' {
|
|
$$=$2;
|
|
}
|
|
;
|
|
|
|
/*
|
|
* Complex types, their bison value is struct f_val
|
|
*/
|
|
fipa:
|
|
IP4 %prec PREFIX_DUMMY { $$.type = T_IP; $$.val.ip = ipa_from_ip4($1); }
|
|
| IP6 %prec PREFIX_DUMMY { $$.type = T_IP; $$.val.ip = ipa_from_ip6($1); }
|
|
;
|
|
|
|
|
|
|
|
/*
|
|
* Set constants. They are also used in switch cases. We use separate
|
|
* nonterminals for switch (set_atom/switch_atom, set_item/switch_item ...)
|
|
* to elude a collision between symbol (in expr) in set_atom and symbol
|
|
* as a function call in switch case cmds.
|
|
*/
|
|
|
|
set_atom:
|
|
NUM { $$.type = T_INT; $$.val.i = $1; }
|
|
| fipa { $$ = $1; }
|
|
| VPN_RD { $$.type = T_RD; $$.val.ec = $1; }
|
|
| ENUM { $$.type = pair_a($1); $$.val.i = pair_b($1); }
|
|
| '(' term ')' {
|
|
if (f_eval(f_linearize($2), cfg_mem, &($$)) > F_RETURN) cf_error("Runtime error");
|
|
if (!f_valid_set_type($$.type)) cf_error("Set-incompatible type");
|
|
}
|
|
| CF_SYM_KNOWN {
|
|
cf_assert_symbol($1, SYM_CONSTANT);
|
|
if (!f_valid_set_type(SYM_TYPE($1))) cf_error("%s: set-incompatible type", $1->name);
|
|
$$ = *$1->val;
|
|
}
|
|
;
|
|
|
|
switch_atom:
|
|
NUM { $$.type = T_INT; $$.val.i = $1; }
|
|
| '(' term ')' { $$.type = T_INT; $$.val.i = f_eval_int(f_linearize($2)); }
|
|
| fipa { $$ = $1; }
|
|
| ENUM { $$.type = pair_a($1); $$.val.i = pair_b($1); }
|
|
;
|
|
|
|
cnum:
|
|
term { $$ = f_eval_int(f_linearize($1)); }
|
|
|
|
pair_item:
|
|
'(' cnum ',' cnum ')' { $$ = f_new_pair_item($2, $2, $4, $4); }
|
|
| '(' cnum ',' cnum DDOT cnum ')' { $$ = f_new_pair_item($2, $2, $4, $6); }
|
|
| '(' cnum ',' '*' ')' { $$ = f_new_pair_item($2, $2, 0, CC_ALL); }
|
|
| '(' cnum DDOT cnum ',' cnum ')' { $$ = f_new_pair_set($2, $4, $6, $6); }
|
|
| '(' cnum DDOT cnum ',' cnum DDOT cnum ')' { $$ = f_new_pair_set($2, $4, $6, $8); }
|
|
| '(' cnum DDOT cnum ',' '*' ')' { $$ = f_new_pair_item($2, $4, 0, CC_ALL); }
|
|
| '(' '*' ',' cnum ')' { $$ = f_new_pair_set(0, CC_ALL, $4, $4); }
|
|
| '(' '*' ',' cnum DDOT cnum ')' { $$ = f_new_pair_set(0, CC_ALL, $4, $6); }
|
|
| '(' '*' ',' '*' ')' { $$ = f_new_pair_item(0, CC_ALL, 0, CC_ALL); }
|
|
| '(' cnum ',' cnum ')' DDOT '(' cnum ',' cnum ')'
|
|
{ $$ = f_new_pair_item($2, $8, $4, $10); }
|
|
;
|
|
|
|
ec_kind:
|
|
RT { $$ = EC_RT; }
|
|
| RO { $$ = EC_RO; }
|
|
| UNKNOWN NUM { $$ = $2; }
|
|
| GENERIC { $$ = EC_GENERIC; }
|
|
;
|
|
|
|
ec_item:
|
|
'(' ec_kind ',' cnum ',' cnum ')' { $$ = f_new_ec_item($2, 0, $4, $6, $6); }
|
|
| '(' ec_kind ',' cnum ',' cnum DDOT cnum ')' { $$ = f_new_ec_item($2, 0, $4, $6, $8); }
|
|
| '(' ec_kind ',' cnum ',' '*' ')' { $$ = f_new_ec_item($2, 0, $4, 0, EC_ALL); }
|
|
;
|
|
|
|
lc_item:
|
|
'(' cnum ',' cnum ',' cnum ')' { $$ = f_new_lc_item($2, $2, $4, $4, $6, $6); }
|
|
| '(' cnum ',' cnum ',' cnum DDOT cnum ')' { $$ = f_new_lc_item($2, $2, $4, $4, $6, $8); }
|
|
| '(' cnum ',' cnum ',' '*' ')' { $$ = f_new_lc_item($2, $2, $4, $4, 0, LC_ALL); }
|
|
| '(' cnum ',' cnum DDOT cnum ',' '*' ')' { $$ = f_new_lc_item($2, $2, $4, $6, 0, LC_ALL); }
|
|
| '(' cnum ',' '*' ',' '*' ')' { $$ = f_new_lc_item($2, $2, 0, LC_ALL, 0, LC_ALL); }
|
|
| '(' cnum DDOT cnum ',' '*' ',' '*' ')' { $$ = f_new_lc_item($2, $4, 0, LC_ALL, 0, LC_ALL); }
|
|
| '(' '*' ',' '*' ',' '*' ')' { $$ = f_new_lc_item(0, LC_ALL, 0, LC_ALL, 0, LC_ALL); }
|
|
| '(' cnum ',' cnum ',' cnum ')' DDOT '(' cnum ',' cnum ',' cnum ')'
|
|
{ $$ = f_new_lc_item($2, $10, $4, $12, $6, $14); }
|
|
;
|
|
|
|
set_item:
|
|
pair_item
|
|
| ec_item
|
|
| lc_item
|
|
| set_atom { $$ = f_new_item($1, $1); }
|
|
| set_atom DDOT set_atom { $$ = f_new_item($1, $3); }
|
|
;
|
|
|
|
switch_item:
|
|
pair_item
|
|
| ec_item
|
|
| lc_item
|
|
| switch_atom { $$ = f_new_item($1, $1); }
|
|
| switch_atom DDOT switch_atom { $$ = f_new_item($1, $3); }
|
|
;
|
|
|
|
set_items:
|
|
set_item
|
|
| set_items ',' set_item { $$ = f_merge_items($1, $3); }
|
|
;
|
|
|
|
switch_items:
|
|
switch_item
|
|
| switch_items ',' switch_item { $$ = f_merge_items($1, $3); }
|
|
;
|
|
|
|
fprefix:
|
|
net_ip_ { $$.net = $1; $$.lo = $1.pxlen; $$.hi = $1.pxlen; }
|
|
| net_ip_ '+' { $$.net = $1; $$.lo = $1.pxlen; $$.hi = net_max_prefix_length[$1.type]; }
|
|
| net_ip_ '-' { $$.net = $1; $$.lo = 0; $$.hi = $1.pxlen; }
|
|
| net_ip_ '{' NUM ',' NUM '}' {
|
|
$$.net = $1; $$.lo = $3; $$.hi = $5;
|
|
if (($3 > $5) || ($5 > net_max_prefix_length[$1.type]))
|
|
cf_error("Invalid prefix pattern range: {%u, %u}", $3, $5);
|
|
}
|
|
;
|
|
|
|
fprefix_set:
|
|
fprefix { $$ = f_new_trie(cfg_mem, 0); trie_add_prefix($$, &($1.net), $1.lo, $1.hi); }
|
|
| fprefix_set ',' fprefix { $$ = $1; if (!trie_add_prefix($$, &($3.net), $3.lo, $3.hi)) cf_error("Mixed IPv4/IPv6 prefixes in prefix set"); }
|
|
;
|
|
|
|
switch_body: /* EMPTY */ { $$ = NULL; }
|
|
| switch_body switch_items ':' cmds {
|
|
/* Fill data fields */
|
|
struct f_tree *t;
|
|
struct f_line *line = f_linearize($4);
|
|
for (t = $2; t; t = t->left)
|
|
t->data = line;
|
|
$$ = f_merge_items($1, $2);
|
|
}
|
|
| switch_body ELSECOL cmds {
|
|
struct f_tree *t = f_new_tree();
|
|
t->from.type = t->to.type = T_VOID;
|
|
t->right = t;
|
|
t->data = f_linearize($3);
|
|
$$ = f_merge_items($1, t);
|
|
}
|
|
;
|
|
|
|
bgp_path_expr:
|
|
symbol_value { $$ = $1; }
|
|
| '(' term ')' { $$ = $2; }
|
|
;
|
|
|
|
bgp_path:
|
|
PO bgp_path_tail PC { $$ = $2; }
|
|
;
|
|
|
|
bgp_path_tail:
|
|
NUM bgp_path_tail { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_PATH_MASK_ITEM, .val.pmi = { .asn = $1, .kind = PM_ASN, }, }); $$->next = $2; }
|
|
| NUM DDOT NUM bgp_path_tail { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_PATH_MASK_ITEM, .val.pmi = { .from = $1, .to = $3, .kind = PM_ASN_RANGE }, }); $$->next = $4; }
|
|
| '[' set_items ']' bgp_path_tail {
|
|
if ($2->from.type != T_INT) cf_error("Only integer sets allowed in path mask");
|
|
$$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_PATH_MASK_ITEM, .val.pmi = { .set = build_tree($2), .kind = PM_ASN_SET }, }); $$->next = $4;
|
|
}
|
|
| '*' 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; }
|
|
;
|
|
|
|
constant:
|
|
NUM { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_INT, .val.i = $1, }); }
|
|
| TRUE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 1, }); }
|
|
| FALSE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 0, }); }
|
|
| TEXT { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_STRING, .val.s = $1, }); }
|
|
| fipa { $$ = f_new_inst(FI_CONSTANT, $1); }
|
|
| VPN_RD { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_RD, .val.ec = $1, }); }
|
|
| net_ { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_NET, .val.net = $1, }); }
|
|
| '[' set_items ']' {
|
|
DBG( "We've got a set here..." );
|
|
$$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_SET, .val.t = build_tree($2), });
|
|
DBG( "ook\n" );
|
|
}
|
|
| '[' fprefix_set ']' { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_PREFIX_SET, .val.ti = $2, }); }
|
|
| ENUM { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = $1 >> 16, .val.i = $1 & 0xffff, }); }
|
|
;
|
|
|
|
constructor:
|
|
'(' term ',' term ')' { $$ = f_new_inst(FI_PAIR_CONSTRUCT, $2, $4); }
|
|
| '(' ec_kind ',' term ',' term ')' { $$ = f_new_inst(FI_EC_CONSTRUCT, $4, $6, $2); }
|
|
| '(' term ',' term ',' term ')' { $$ = f_new_inst(FI_LC_CONSTRUCT, $2, $4, $6); }
|
|
| bgp_path { $$ = f_new_inst(FI_PATHMASK_CONSTRUCT, $1); }
|
|
;
|
|
|
|
|
|
/* This generates the function_call variable list backwards. */
|
|
var_list: /* EMPTY */ { $$ = NULL; }
|
|
| term { $$ = $1; }
|
|
| var_list ',' term { $$ = $3; $$->next = $1; }
|
|
|
|
function_call:
|
|
CF_SYM_KNOWN '(' var_list ')' {
|
|
if ($1->class != SYM_FUNCTION)
|
|
cf_error("You can't call something which is not a function. Really.");
|
|
|
|
struct f_inst *fc = f_new_inst(FI_CALL, $1);
|
|
uint args = 0;
|
|
while ($3) {
|
|
args++;
|
|
struct f_inst *tmp = $3->next;
|
|
$3->next = fc;
|
|
|
|
fc = $3;
|
|
$3 = tmp;
|
|
}
|
|
|
|
if (args != $1->function->args)
|
|
cf_error("Function call '%s' got %u arguments, need %u arguments.",
|
|
$1->name, args, $1->function->args);
|
|
|
|
$$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_VOID });
|
|
$$->next = fc;
|
|
}
|
|
;
|
|
|
|
symbol_value: CF_SYM_KNOWN
|
|
{
|
|
switch ($1->class) {
|
|
case SYM_CONSTANT_RANGE:
|
|
$$ = f_new_inst(FI_CONSTANT, *($1->val));
|
|
break;
|
|
case SYM_VARIABLE_RANGE:
|
|
$$ = f_new_inst(FI_VAR_GET, $1);
|
|
break;
|
|
case SYM_ATTRIBUTE:
|
|
$$ = f_new_inst(FI_EA_GET, *$1->attribute);
|
|
break;
|
|
default:
|
|
cf_error("Can't get value of symbol %s", $1->name);
|
|
}
|
|
}
|
|
;
|
|
|
|
static_attr:
|
|
FROM { $$ = f_new_static_attr(T_IP, SA_FROM, 0); }
|
|
| GW { $$ = f_new_static_attr(T_IP, SA_GW, 0); }
|
|
| NET { $$ = f_new_static_attr(T_NET, SA_NET, 1); }
|
|
| PROTO { $$ = f_new_static_attr(T_STRING, SA_PROTO, 1); }
|
|
| SOURCE { $$ = f_new_static_attr(T_ENUM_RTS, SA_SOURCE, 1); }
|
|
| SCOPE { $$ = f_new_static_attr(T_ENUM_SCOPE, SA_SCOPE, 0); }
|
|
| DEST { $$ = f_new_static_attr(T_ENUM_RTD, SA_DEST, 0); }
|
|
| IFNAME { $$ = f_new_static_attr(T_STRING, SA_IFNAME, 0); }
|
|
| IFINDEX { $$ = f_new_static_attr(T_INT, SA_IFINDEX, 1); }
|
|
;
|
|
|
|
term:
|
|
'(' term ')' { $$ = $2; }
|
|
| term '+' term { $$ = f_new_inst(FI_ADD, $1, $3); }
|
|
| term '-' term { $$ = f_new_inst(FI_SUBTRACT, $1, $3); }
|
|
| term '*' term { $$ = f_new_inst(FI_MULTIPLY, $1, $3); }
|
|
| term '/' term { $$ = f_new_inst(FI_DIVIDE, $1, $3); }
|
|
| term AND term { $$ = f_new_inst(FI_AND, $1, $3); }
|
|
| term OR term { $$ = f_new_inst(FI_OR, $1, $3); }
|
|
| term '=' term { $$ = f_new_inst(FI_EQ, $1, $3); }
|
|
| term NEQ term { $$ = f_new_inst(FI_NEQ, $1, $3); }
|
|
| term '<' term { $$ = f_new_inst(FI_LT, $1, $3); }
|
|
| term LEQ term { $$ = f_new_inst(FI_LTE, $1, $3); }
|
|
| term '>' term { $$ = f_new_inst(FI_LT, $3, $1); }
|
|
| term GEQ term { $$ = f_new_inst(FI_LTE, $3, $1); }
|
|
| term '~' term { $$ = f_new_inst(FI_MATCH, $1, $3); }
|
|
| term NMA term { $$ = f_new_inst(FI_NOT_MATCH, $1, $3); }
|
|
| '!' term { $$ = f_new_inst(FI_NOT, $2); }
|
|
| DEFINED '(' term ')' { $$ = f_new_inst(FI_DEFINED, $3); }
|
|
|
|
| symbol_value { $$ = $1; }
|
|
| constant { $$ = $1; }
|
|
| constructor { $$ = $1; }
|
|
|
|
| PREFERENCE { $$ = f_new_inst(FI_PREF_GET); }
|
|
|
|
| static_attr { $$ = f_new_inst(FI_RTA_GET, $1); }
|
|
|
|
| dynamic_attr { $$ = f_new_inst(FI_EA_GET, $1); }
|
|
|
|
| term '.' IS_V4 { $$ = f_new_inst(FI_IS_V4, $1); }
|
|
| term '.' TYPE { $$ = f_new_inst(FI_TYPE, $1); }
|
|
| term '.' IP { $$ = f_new_inst(FI_IP, $1); }
|
|
| term '.' RD { $$ = f_new_inst(FI_ROUTE_DISTINGUISHER, $1); }
|
|
| term '.' LEN { $$ = f_new_inst(FI_LENGTH, $1); }
|
|
| term '.' MAXLEN { $$ = f_new_inst(FI_ROA_MAXLEN, $1); }
|
|
| term '.' ASN { $$ = f_new_inst(FI_ROA_ASN, $1); }
|
|
| term '.' SRC { $$ = f_new_inst(FI_NET_SRC, $1); }
|
|
| term '.' DST { $$ = f_new_inst(FI_NET_DST, $1); }
|
|
| term '.' MASK '(' term ')' { $$ = f_new_inst(FI_IP_MASK, $1, $5); }
|
|
| term '.' FIRST { $$ = f_new_inst(FI_AS_PATH_FIRST, $1); }
|
|
| term '.' LAST { $$ = f_new_inst(FI_AS_PATH_LAST, $1); }
|
|
| term '.' LAST_NONAGGREGATED { $$ = f_new_inst(FI_AS_PATH_LAST_NAG, $1); }
|
|
|
|
/* Communities */
|
|
/* This causes one shift/reduce conflict
|
|
| dynamic_attr '.' ADD '(' term ')' { }
|
|
| dynamic_attr '.' DELETE '(' term ')' { }
|
|
| dynamic_attr '.' CONTAINS '(' term ')' { }
|
|
| dynamic_attr '.' RESET{ }
|
|
*/
|
|
|
|
| '+' EMPTY '+' { $$ = f_new_inst(FI_CONSTANT, f_const_empty_path); }
|
|
| '-' EMPTY '-' { $$ = f_new_inst(FI_CONSTANT, f_const_empty_clist); }
|
|
| '-' '-' EMPTY '-' '-' { $$ = f_new_inst(FI_CONSTANT, f_const_empty_eclist); }
|
|
| '-' '-' '-' EMPTY '-' '-' '-' { $$ = f_new_inst(FI_CONSTANT, f_const_empty_lclist); }
|
|
| PREPEND '(' term ',' term ')' { $$ = f_new_inst(FI_PATH_PREPEND, $3, $5); }
|
|
| ADD '(' term ',' term ')' { $$ = f_new_inst(FI_CLIST_ADD, $3, $5); }
|
|
| DELETE '(' term ',' term ')' { $$ = f_new_inst(FI_CLIST_DEL, $3, $5); }
|
|
| FILTER '(' term ',' term ')' { $$ = f_new_inst(FI_CLIST_FILTER, $3, $5); }
|
|
|
|
| ROA_CHECK '(' rtable ')' { $$ = f_new_inst(FI_ROA_CHECK_IMPLICIT, $3); }
|
|
| ROA_CHECK '(' rtable ',' term ',' term ')' { $$ = f_new_inst(FI_ROA_CHECK_EXPLICIT, $5, $7, $3); }
|
|
|
|
| FORMAT '(' term ')' { $$ = f_new_inst(FI_FORMAT, $3); }
|
|
|
|
/* | term '.' LEN { $$->code = P('P','l'); } */
|
|
|
|
| function_call
|
|
;
|
|
|
|
break_command:
|
|
ACCEPT { $$ = F_ACCEPT; }
|
|
| REJECT { $$ = F_REJECT; }
|
|
| ERROR { $$ = F_ERROR; }
|
|
;
|
|
|
|
print_list: /* EMPTY */ { $$ = NULL; }
|
|
| term { $$ = $1; }
|
|
| term ',' print_list {
|
|
ASSERT($1);
|
|
ASSERT($1->next == NULL);
|
|
$1->next = $3;
|
|
$$ = $1;
|
|
}
|
|
;
|
|
|
|
cmd:
|
|
IF term THEN block {
|
|
$$ = f_new_inst(FI_CONDITION, $2, $4, NULL);
|
|
}
|
|
| IF term THEN block ELSE block {
|
|
$$ = f_new_inst(FI_CONDITION, $2, $4, $6);
|
|
}
|
|
| CF_SYM_KNOWN '=' term ';' {
|
|
switch ($1->class) {
|
|
case SYM_VARIABLE_RANGE:
|
|
$$ = f_new_inst(FI_VAR_SET, $3, $1);
|
|
break;
|
|
case SYM_ATTRIBUTE:
|
|
$$ = f_new_inst(FI_EA_SET, $3, *$1->attribute);
|
|
break;
|
|
default:
|
|
cf_error("Can't assign to symbol %s", $1->name);
|
|
}
|
|
}
|
|
| RETURN term ';' {
|
|
DBG( "Ook, we'll return the value\n" );
|
|
$$ = f_new_inst(FI_RETURN, $2);
|
|
}
|
|
| dynamic_attr '=' term ';' {
|
|
$$ = f_new_inst(FI_EA_SET, $3, $1);
|
|
}
|
|
| static_attr '=' term ';' {
|
|
if ($1.readonly)
|
|
cf_error( "This static attribute is read-only.");
|
|
$$ = f_new_inst(FI_RTA_SET, $3, $1);
|
|
}
|
|
| PREFERENCE '=' term ';' {
|
|
$$ = f_new_inst(FI_PREF_SET, $3);
|
|
}
|
|
| UNSET '(' dynamic_attr ')' ';' {
|
|
$$ = f_new_inst(FI_EA_UNSET, $3);
|
|
}
|
|
| break_command print_list ';' {
|
|
struct f_inst *breaker = f_new_inst(FI_DIE, $1);
|
|
if ($2) {
|
|
struct f_inst *printer = f_new_inst(FI_PRINT, $2);
|
|
struct f_inst *flusher = f_new_inst(FI_FLUSH);
|
|
printer->next = flusher;
|
|
flusher->next = breaker;
|
|
$$ = printer;
|
|
} else
|
|
$$ = breaker;
|
|
}
|
|
| PRINT print_list ';' {
|
|
$$ = f_new_inst(FI_PRINT, $2);
|
|
$$->next = f_new_inst(FI_FLUSH);
|
|
}
|
|
| PRINTN print_list ';' {
|
|
$$ = f_new_inst(FI_PRINT, $2);
|
|
}
|
|
| function_call ';' { $$ = f_new_inst(FI_DROP_RESULT, $1); }
|
|
| CASE term '{' switch_body '}' {
|
|
$$ = f_new_inst(FI_SWITCH, $2, build_tree($4));
|
|
}
|
|
|
|
| dynamic_attr '.' EMPTY ';' { $$ = f_generate_empty($1); }
|
|
| dynamic_attr '.' PREPEND '(' term ')' ';' { $$ = f_generate_complex( FI_PATH_PREPEND, $1, $5 ); }
|
|
| dynamic_attr '.' ADD '(' term ')' ';' { $$ = f_generate_complex( FI_CLIST_ADD, $1, $5 ); }
|
|
| dynamic_attr '.' DELETE '(' term ')' ';' { $$ = f_generate_complex( FI_CLIST_DEL, $1, $5 ); }
|
|
| dynamic_attr '.' FILTER '(' term ')' ';' { $$ = f_generate_complex( FI_CLIST_FILTER, $1, $5 ); }
|
|
| BT_ASSERT '(' get_cf_position term get_cf_position ')' ';' { $$ = assert_done($4, $3 + 1, $5 - 1); }
|
|
| BT_CHECK_ASSIGN '(' get_cf_position lvalue get_cf_position ',' term ')' ';' { $$ = assert_assign(&$4, $7, $3 + 1, $5 - 1); }
|
|
;
|
|
|
|
get_cf_position:
|
|
{
|
|
$$ = cf_text;
|
|
};
|
|
|
|
lvalue:
|
|
CF_SYM_KNOWN { cf_assert_symbol($1, SYM_VARIABLE); $$ = (struct f_lval) { .type = F_LVAL_VARIABLE, .sym = $1 }; }
|
|
| PREFERENCE { $$ = (struct f_lval) { .type = F_LVAL_PREFERENCE }; }
|
|
| static_attr { $$ = (struct f_lval) { .type = F_LVAL_SA, .sa = $1 }; }
|
|
| dynamic_attr { $$ = (struct f_lval) { .type = F_LVAL_EA, .da = $1 }; };
|
|
|
|
CF_END
|