0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-09-16 18:35:19 +00:00

Merge branch 'thread-next' into HEAD

This commit is contained in:
Maria Matejka 2023-11-02 14:43:15 +01:00
commit c6fba7d7e6
132 changed files with 6964 additions and 1362 deletions

View File

@ -60,14 +60,6 @@ stages:
- linux
- amd64
build-debian-8-amd64:
<<: *build-linux
image: registry.nic.cz/labs/bird:debian-8-amd64
build-debian-8-i386:
<<: *build-linux
image: registry.nic.cz/labs/bird:debian-8-i386
build-debian-9-amd64:
<<: *build-linux
image: registry.nic.cz/labs/bird:debian-9-amd64
@ -224,28 +216,6 @@ build-opensuse-15.3-amd64:
paths:
- pkg/pkgs/*
# Dpkg error: PATH is not set
#pkg-debian-8-amd64:
# <<: *pkg-deb
# needs: [build-debian-8-amd64]
# image: registry.nic.cz/labs/bird:debian-8-amd64
# Dpkg error: PATH is not set
#pkg-debian-8-i386:
# <<: *pkg-deb
# needs: [build-debian-8-i386]
# image: registry.nic.cz/labs/bird:debian-8-i386
pkg-debian-9-amd64:
<<: *pkg-deb
needs: [build-debian-9-amd64]
image: registry.nic.cz/labs/bird:debian-9-amd64
pkg-debian-9-i386:
<<: *pkg-deb
needs: [build-debian-9-i386]
image: registry.nic.cz/labs/bird:debian-9-i386
pkg-debian-10-amd64:
<<: *pkg-deb
needs: [build-debian-10-amd64]
@ -284,7 +254,7 @@ pkg-fedora-33-amd64:
pkg-fedora-34-amd64:
<<: *pkg-rpm
needs: [build-fedora-34-amd64]
image: registry.labs.nic.cz/labs/bird:fedora-34-amd64
image: registry.nic.cz/labs/bird:fedora-34-amd64
pkg-centos-8-amd64:
<<: *pkg-rpm-wa
@ -465,11 +435,11 @@ test-ibgp-loop:
variables:
TEST_NAME: cf-ibgp-loop
test-ibgp-loop-big:
<<: *test-base
variables:
TEST_NAME: cf-ibgp-loop-big
#test-ibgp-loop-big:
# <<: *test-base
# variables:
# TEST_NAME: cf-ibgp-loop-big
#
test-ibgp-flat:
<<: *test-base
variables:

26
NEWS
View File

@ -29,6 +29,32 @@ Version 3.0-alpha0 (2022-02-07)
o Lots of refactoring
o Bugfixes and improvements as they came along
Version 2.13.1 (2023-06-23)
o BGP: Fix role check when no capability option is present
o Filter: Fixed segfault when a case option had an empty block
This is a bugfix version.
Version 2.13 (2023-04-21)
o Babel: IPv4 via IPv6 extension (RFC 9229)
o Babel: Improve authentication on lossy networks
o BGP: New 'allow bgp_med' option
o BSD: Support for IPv4 routes with IPv6 nexthop on FreeBSD
o Experimental BMP protocol implementation
o Important bugfixes
Notes:
We changed versioning scheme from <epoch>.<major>.<minor> to more common
<major>.<minor>.<patch> . From now on, you may expect that BIRD 2.13.x will be
strictly only fixing bugs found in 2.13, whereas BIRD 2.14 will also contain
new features.
This BIRD version contains an alpha release of BMP protocol implementation.
It is not ready for production usage and therefore it is not compiled by
default and have to be enabled during installation.
Version 2.0.12 (2023-01-23)
o Filter: New 'onlink' route attribute
o Compile-time option to use 4-way tries instead of 16-way ones

View File

@ -34,6 +34,7 @@ class BIRDFValPrinter(BIRDPrinter):
"T_IP": "ip",
"T_NET": "net",
"T_STRING": "s",
"T_BYTESTRING": "bs",
"T_PATH_MASK": "path_mask",
"T_PATH": "ad",
"T_CLIST": "ad",

View File

@ -42,7 +42,7 @@
#define PARSER 1
#include "nest/bird.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/protocol.h"
#include "filter/filter.h"
#include "filter/f-inst.h"
@ -51,12 +51,6 @@
#include "lib/string.h"
#include "lib/hash.h"
struct keyword {
byte *name;
int value;
struct keyword *next;
};
#include "conf/keywords.h"
/* Could be defined by Bison in cf-parse.tab.h, inteferes with SYM hash */
@ -67,16 +61,10 @@ struct keyword {
static uint cf_hash(const byte *c);
#define KW_KEY(n) n->name
#define KW_NEXT(n) n->next
#define KW_EQ(a,b) !strcmp(a,b)
#define KW_FN(k) cf_hash(k)
#define KW_ORDER 8 /* Fixed */
#define SYM_KEY(n) n->name, n->scope->active
#define SYM_KEY(n) n->name
#define SYM_NEXT(n) n->next
#define SYM_EQ(a,s1,b,s2) !strcmp(a,b) && s1 == s2
#define SYM_FN(k,s) cf_hash(k)
#define SYM_EQ(a,b) !strcmp(a,b)
#define SYM_FN(k) cf_hash(k)
#define SYM_ORDER 4 /* Initial */
#define SYM_REHASH sym_rehash
@ -85,14 +73,19 @@ static uint cf_hash(const byte *c);
HASH_DEFINE_REHASH_FN(SYM, struct symbol)
HASH(struct keyword) kw_hash;
HASH(struct ea_class) ea_name_hash;
/* Global symbol scopes */
pool *global_root_scope_pool;
linpool *global_root_scope_linpool;
struct sym_scope
global_root_scope = {
},
global_filter_scope = {
.next = &global_root_scope,
};
/* Local symbol scope: TODO this isn't thread-safe */
struct sym_scope *conf_this_scope;
static struct sym_scope global_root_scope__init = { .active = 1, };
struct sym_scope *global_root_scope = &global_root_scope__init;
linpool *cfg_mem;
int (*cf_read_hook)(byte *buf, unsigned int max, int fd);
@ -258,35 +251,24 @@ WHITE [ \t]
return IP4;
}
{XIGIT}{2}((:{XIGIT}{2}){15,}|({XIGIT}{2}){15,}) {
({XIGIT}{2}){16,}|{XIGIT}{2}(:{XIGIT}{2}){15,}|hex:({XIGIT}{2}(:?{XIGIT}{2})*)? {
char *s = yytext;
size_t len = 0, i;
struct bytestring *bytes;
byte *b;
struct adata *bs;
while (*s) {
len++;
s += 2;
if (*s == ':')
s++;
}
bytes = cfg_allocz(sizeof(*bytes) + len);
/* Skip 'hex:' prefix */
if (s[0] == 'h' && s[1] == 'e' && s[2] == 'x' && s[3] == ':')
s += 4;
bytes->length = len;
b = &bytes->data[0];
s = yytext;
errno = 0;
for (i = 0; i < len; i++) {
*b = bstrtobyte16(s);
if (errno == ERANGE)
cf_error("Invalid hex string");
b++;
s += 2;
if (*s == ':')
s++;
}
cf_lval.bs = bytes;
return BYTESTRING;
int len = bstrhextobin(s, NULL);
if (len < 0)
cf_error("Invalid hex string");
bs = cfg_allocz(sizeof(struct adata) + len);
bs->length = bstrhextobin(s, bs->data);
ASSERT(bs->length == len);
cf_lval.bs = bs;
return BYTETEXT;
}
({XIGIT}*::|({XIGIT}*:){3,})({XIGIT}*|{DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+) {
@ -402,6 +384,7 @@ else: {
\>\= return GEQ;
\&\& return AND;
\|\| return OR;
\-\> return IMP;
\[\= return PO;
\=\] return PC;
@ -577,49 +560,56 @@ check_eof(void)
return 0;
}
static inline void cf_swap_soft_scope(void);
static inline void cf_swap_soft_scope(struct config *conf);
static struct symbol *
cf_new_symbol(const byte *c)
struct symbol *
cf_new_symbol(struct sym_scope *scope, pool *p, struct linpool *lp, const byte *c)
{
if (scope->readonly)
cf_error("Unknown symbol %s", c);
struct symbol *s;
uint l = strlen(c);
if (l > SYM_MAX_LEN)
cf_error("Symbol too long");
cf_swap_soft_scope();
s = lp_alloc(lp, sizeof(struct symbol) + l + 1);
*s = (struct symbol) { .scope = scope, .class = SYM_VOID, };
memcpy(s->name, c, l+1);
s = cfg_allocz(sizeof(struct symbol) + l + 1);
*s = (struct symbol) { .scope = conf_this_scope, .class = SYM_VOID, };
strcpy(s->name, c);
if (!scope->hash.data)
HASH_INIT(scope->hash, p, SYM_ORDER);
if (!conf_this_scope->hash.data)
HASH_INIT(conf_this_scope->hash, new_config->pool, SYM_ORDER);
HASH_INSERT2(scope->hash, SYM, p, s);
HASH_INSERT2(conf_this_scope->hash, SYM, new_config->pool, s);
if (conf_this_scope == new_config->root_scope)
if (new_config && (scope == new_config->root_scope))
add_tail(&(new_config->symbols), &(s->n));
return s;
}
static struct symbol *
cf_root_symbol(const byte *c)
struct symbol *
cf_root_symbol(const byte *c, struct sym_scope *ss)
{
uint l = strlen(c);
if (l > SYM_MAX_LEN)
bug("Root symbol %s too long", c);
struct symbol *s = mb_alloc(&root_pool, sizeof(struct symbol) + l + 1);
*s = (struct symbol) { .scope = global_root_scope, .class = SYM_VOID, };
if (!global_root_scope_pool)
{
global_root_scope_pool = rp_new(&root_pool, the_bird_domain.the_bird, "Keywords pool");
global_root_scope_linpool = lp_new(global_root_scope_pool);
}
struct symbol *s = lp_alloc(global_root_scope_linpool, sizeof(struct symbol) + l + 1);
*s = (struct symbol) { .scope = ss, .class = SYM_VOID, };
memcpy(s->name, c, l+1);
if (!global_root_scope->hash.data)
HASH_INIT(global_root_scope->hash, &root_pool, SYM_ORDER);
if (!ss->hash.data)
HASH_INIT(ss->hash, &root_pool, SYM_ORDER);
HASH_INSERT2(global_root_scope->hash, SYM, &root_pool, s);
HASH_INSERT2(ss->hash, SYM, &root_pool, s);
return s;
}
@ -642,7 +632,8 @@ cf_find_symbol_scope(const struct sym_scope *scope, const byte *c)
/* Find the symbol here or anywhere below */
while (scope)
if (scope->hash.data && (s = HASH_FIND(scope->hash, SYM, c, 1)))
if (((scope != &global_filter_scope) || !new_config || new_config->allow_attributes) &&
scope->hash.data && (s = HASH_FIND(scope->hash, SYM, c)))
return s;
else
scope = scope->next;
@ -660,9 +651,12 @@ cf_find_symbol_scope(const struct sym_scope *scope, const byte *c)
* existing symbol is found.
*/
struct symbol *
cf_get_symbol(const byte *c)
cf_get_symbol(struct config *conf, const byte *c)
{
return cf_find_symbol_scope(conf_this_scope, c) ?: cf_new_symbol(c);
return cf_find_symbol_scope(conf->current_scope, c) ?: (
cf_swap_soft_scope(conf),
cf_new_symbol(conf->current_scope, conf->pool, conf->mem, c)
);
}
/**
@ -673,22 +667,23 @@ cf_get_symbol(const byte *c)
* for purposes of cf_define_symbol().
*/
struct symbol *
cf_localize_symbol(struct symbol *sym)
cf_localize_symbol(struct config *conf, struct symbol *sym)
{
/* If the symbol type is void, it has been recently allocated just in this scope. */
if (!sym->class)
return sym;
/* If the scope is the current, it is already defined in this scope. */
if (cf_symbol_is_local(sym))
if (cf_symbol_is_local(conf, sym))
cf_error("Symbol '%s' already defined", sym->name);
/* Not allocated here yet, doing it now. */
return cf_new_symbol(sym->name);
cf_swap_soft_scope(conf);
return cf_new_symbol(conf->current_scope, conf->pool, conf->mem, sym->name);
}
struct symbol *
cf_default_name(char *template, int *counter)
cf_default_name(struct config *conf, char *template, int *counter)
{
char buf[SYM_MAX_LEN];
struct symbol *s;
@ -697,7 +692,7 @@ cf_default_name(char *template, int *counter)
for(;;)
{
bsprintf(buf, template, ++(*counter));
s = cf_get_symbol(buf);
s = cf_get_symbol(conf, buf);
if (s->class == SYM_VOID)
return s;
if (!perc)
@ -710,67 +705,56 @@ static enum yytokentype
cf_lex_symbol(const char *data)
{
/* Have we defined such a symbol? */
struct symbol *sym = cf_get_symbol(data);
struct symbol *sym = cf_get_symbol(new_config, data);
cf_lval.s = sym;
/* Is it a keyword? Prefer the keyword. */
struct keyword *k = HASH_FIND(kw_hash, KW, data);
if (k)
switch (sym->class)
{
if (k->value > 0)
return k->value;
else
case SYM_KEYWORD:
{
cf_lval.i = -k->value;
int val = sym->keyword->value;
if (val > 0) return val;
cf_lval.i = -val;
return ENUM;
}
case SYM_METHOD:
return (sym->method->arg_num > 1) ? CF_SYM_METHOD_ARGS : CF_SYM_METHOD_BARE;
case SYM_VOID:
return CF_SYM_UNDEFINED;
default:
return CF_SYM_KNOWN;
}
/* OK, only a symbol. */
if (sym->class == SYM_VOID)
return CF_SYM_UNDEFINED;
else
return CF_SYM_KNOWN;
}
static void
cf_lex_init_kh(void)
{
HASH_INIT(kw_hash, config_pool, KW_ORDER);
struct keyword *k;
for (k=keyword_list; k->name; k++)
HASH_INSERT(kw_hash, KW, k);
}
void
ea_lex_register(struct ea_class *def)
{
struct symbol *sym = cf_root_symbol(def->name);
sym->class = SYM_ATTRIBUTE;
sym->attribute = def;
def->sym = sym;
def->sym = cf_root_symbol(def->name, &global_filter_scope);
def->sym->class = SYM_ATTRIBUTE;
def->sym->attribute = def;
}
#if 0
/* When we start to support complete protocol removal, we may need this function */
void
ea_lex_unregister(struct ea_class *def)
{
struct symbol *sym = def->sym;
HASH_REMOVE2(global_root_scope->hash, SYM, &root_pool, sym);
HASH_REMOVE2(global_filter_scope.hash, SYM, &root_pool, sym);
mb_free(sym);
def->sym = NULL;
}
#endif
struct ea_class *
ea_class_find_by_name(const char *name)
{
struct symbol *sym = cf_find_symbol(global_root_scope, name);
if (!sym || (sym->class != SYM_ATTRIBUTE))
return NULL;
else
return sym->attribute;
struct symbol *sym = cf_find_symbol_scope(config ? config->root_scope : &global_filter_scope, name);
return sym && (sym->class == SYM_ATTRIBUTE) ? sym->attribute : NULL;
}
void f_type_methods_register(void);
/**
* cf_lex_init - initialize the lexer
* @is_cli: true if we're going to parse CLI command, false for configuration
@ -782,8 +766,20 @@ ea_class_find_by_name(const char *name)
void
cf_lex_init(int is_cli, struct config *c)
{
if (!kw_hash.data)
cf_lex_init_kh();
if (!global_root_scope.readonly)
{
for (const struct keyword *k = keyword_list; k->name; k++)
{
struct symbol *s = cf_root_symbol(k->name, &global_root_scope);
s->class = SYM_KEYWORD;
s->keyword = k;
}
global_root_scope.readonly = 1;
global_filter_scope.readonly = 1;
f_type_methods_register();
}
ifs_head = ifs = push_ifs(NULL);
if (!is_cli)
@ -801,14 +797,12 @@ cf_lex_init(int is_cli, struct config *c)
else
BEGIN(INITIAL);
c->root_scope = cfg_allocz(sizeof(struct sym_scope));
conf_this_scope = c->root_scope;
conf_this_scope->active = 1;
c->root_scope = c->current_scope = cfg_allocz(sizeof(struct sym_scope));
if (is_cli)
conf_this_scope->next = config->root_scope;
c->current_scope->next = config->root_scope;
else
conf_this_scope->next = global_root_scope;
c->current_scope->next = &global_filter_scope;
}
/**
@ -822,13 +816,12 @@ cf_lex_init(int is_cli, struct config *c)
* in all scopes stored on the stack.
*/
void
cf_push_scope(struct symbol *sym)
cf_push_scope(struct config *conf, struct symbol *sym)
{
struct sym_scope *s = cfg_allocz(sizeof(struct sym_scope));
s->next = conf_this_scope;
conf_this_scope = s;
s->active = 1;
s->next = conf->current_scope;
conf->current_scope = s;
s->name = sym;
s->slots = 0;
}
@ -841,14 +834,11 @@ cf_push_scope(struct symbol *sym)
* invisible to the rest of the config.
*/
void
cf_pop_scope(void)
cf_pop_scope(struct config *conf)
{
ASSERT(!conf_this_scope->soft_scopes);
conf_this_scope->active = 0;
conf_this_scope = conf_this_scope->next;
ASSERT(conf_this_scope);
ASSERT(!conf->current_scope->soft_scopes);
conf->current_scope = conf->current_scope->next;
ASSERT(conf->current_scope);
}
/**
@ -859,12 +849,12 @@ cf_pop_scope(void)
* Such scope will be converted to a regular scope on first use.
*/
void
cf_push_soft_scope(void)
cf_push_soft_scope(struct config *conf)
{
if (conf_this_scope->soft_scopes < 0xfe)
conf_this_scope->soft_scopes++;
if (conf->current_scope->soft_scopes < 0xfe)
conf->current_scope->soft_scopes++;
else
cf_push_block_scope();
cf_push_block_scope(conf);
}
/**
@ -873,12 +863,12 @@ cf_push_soft_scope(void)
* Leave a soft scope entered by cf_push_soft_scope().
*/
void
cf_pop_soft_scope(void)
cf_pop_soft_scope(struct config *conf)
{
if (conf_this_scope->soft_scopes)
conf_this_scope->soft_scopes--;
if (conf->current_scope->soft_scopes)
conf->current_scope->soft_scopes--;
else
cf_pop_block_scope();
cf_pop_block_scope(conf);
}
/**
@ -888,15 +878,36 @@ cf_pop_soft_scope(void)
* on first use. It is done automatically by cf_new_symbol().
*/
static inline void
cf_swap_soft_scope(void)
cf_swap_soft_scope(struct config *conf)
{
if (conf_this_scope->soft_scopes)
if (conf->current_scope->soft_scopes)
{
conf_this_scope->soft_scopes--;
cf_push_block_scope();
conf->current_scope->soft_scopes--;
cf_push_block_scope(conf);
}
}
/**
* cf_enter_filters - enable filter / route attributes namespace
*/
void
cf_enter_filters(void)
{
ASSERT_DIE(!new_config->allow_attributes);
new_config->allow_attributes = 1;
}
/**
* cf_exit_filters - disable filter / route attributes namespace
*/
void
cf_exit_filters(void)
{
ASSERT_DIE(new_config->allow_attributes);
new_config->allow_attributes = 0;
}
/**
* cf_symbol_class_name - get name of a symbol class
* @sym: symbol
@ -923,6 +934,8 @@ cf_symbol_class_name(struct symbol *sym)
return "routing table";
case SYM_ATTRIBUTE:
return "custom attribute";
case SYM_KEYWORD:
return "symbol";
case SYM_CONSTANT_RANGE:
return "constant";
case SYM_VARIABLE_RANGE:

View File

@ -46,7 +46,7 @@
#undef LOCAL_DEBUG
#include "nest/bird.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
#include "lib/resource.h"
@ -60,7 +60,8 @@
static jmp_buf conf_jmpbuf;
struct config *config, *new_config;
struct config *config;
_Thread_local struct config *new_config;
pool *config_pool;
static struct config *old_config; /* Old configuration */

View File

@ -57,6 +57,8 @@ struct config {
int thread_count; /* How many worker threads to prefork */
struct sym_scope *root_scope; /* Scope for root symbols */
struct sym_scope *current_scope; /* Current scope where we are actually in while parsing */
int allow_attributes; /* Allow attributes in the current state of configuration parsing */
_Atomic int obstacle_count; /* Number of items blocking freeing of this config */
event done_event; /* Called when obstacle_count reaches zero */
int shutdown; /* This is a pseudo-config for daemon shutdown */
@ -66,7 +68,7 @@ struct config {
/* Please don't use these variables in protocols. Use proto_config->global instead. */
extern struct config *config; /* Currently active configuration */
extern struct config *new_config; /* Configuration being parsed */
extern _Thread_local struct config *new_config; /* Configuration being parsed */
struct config *config_alloc(const char *name);
int config_parse(struct config *);
@ -80,6 +82,7 @@ int config_status(void);
btime config_timer_status(void);
void config_init(void);
void cf_error(const char *msg, ...) NORET;
#define cf_warn(msg, args...) log(L_WARN "%s:%d:%d: " msg, ifs->file_name, ifs->lino, ifs->chno - ifs->toklen + 1, ##args)
void config_add_obstacle(struct config *);
void config_del_obstacle(struct config *);
void order_shutdown(int gr);
@ -112,6 +115,11 @@ void cfg_copy_list(list *dest, list *src, unsigned node_size);
extern int (*cf_read_hook)(byte *buf, uint max, int fd);
struct keyword {
byte *name;
int value;
};
struct symbol {
node n; /* In list of symbols in config */
struct symbol *next;
@ -127,6 +135,8 @@ struct symbol {
struct ea_class *attribute; /* For SYM_ATTRIBUTE */
struct f_val *val; /* For SYM_CONSTANT */
uint offset; /* For SYM_VARIABLE */
const struct keyword *keyword; /* For SYM_KEYWORD */
const struct f_method *method; /* For SYM_METHOD */
};
char name[0];
@ -139,17 +149,17 @@ struct sym_scope {
HASH(struct symbol) hash; /* Local symbol hash */
uint slots; /* Variable slots */
byte active; /* Currently entered */
byte block; /* No independent stack frame */
byte soft_scopes; /* Number of soft scopes above */
byte block:1; /* No independent stack frame */
byte readonly:1; /* Do not add new symbols */
};
extern struct sym_scope *global_root_scope;
void cf_enter_filters(void);
void cf_exit_filters(void);
extern pool *global_root_scope_pool;
extern linpool *global_root_scope_linpool;
struct bytestring {
size_t length;
byte data[];
};
#define SYM_MAX_LEN 64
@ -161,6 +171,8 @@ struct bytestring {
#define SYM_FILTER 4
#define SYM_TABLE 5
#define SYM_ATTRIBUTE 6
#define SYM_KEYWORD 7
#define SYM_METHOD 8
#define SYM_VARIABLE 0x100 /* 0x100-0x1ff are variable types */
#define SYM_VARIABLE_RANGE SYM_VARIABLE ... (SYM_VARIABLE | 0xff)
@ -188,8 +200,6 @@ struct include_file_stack {
extern struct include_file_stack *ifs;
extern struct sym_scope *conf_this_scope;
int cf_lex(void);
void cf_lex_init(int is_cli, struct config *c);
void cf_lex_unwind(void);
@ -203,12 +213,16 @@ static inline struct symbol *cf_find_symbol_cfg(const struct config *cfg, const
struct sym_scope: cf_find_symbol_scope \
)((where), (what))
struct symbol *cf_get_symbol(const byte *c);
struct symbol *cf_default_name(char *template, int *counter);
struct symbol *cf_localize_symbol(struct symbol *sym);
struct symbol *cf_get_symbol(struct config *conf, const byte *c);
struct symbol *cf_default_name(struct config *conf, char *template, int *counter);
struct symbol *cf_localize_symbol(struct config *conf, struct symbol *sym);
static inline int cf_symbol_is_local(struct symbol *sym)
{ return (sym->scope == conf_this_scope) && !conf_this_scope->soft_scopes; }
static inline int cf_symbol_is_local(struct config *conf, struct symbol *sym)
{ return (sym->scope == conf->current_scope) && !conf->current_scope->soft_scopes; }
/* internal */
struct symbol *cf_new_symbol(struct sym_scope *scope, pool *p, struct linpool *lp, const byte *c);
struct symbol *cf_root_symbol(const byte *, struct sym_scope *);
/**
* cf_define_symbol - define meaning of a symbol
@ -225,22 +239,22 @@ static inline int cf_symbol_is_local(struct symbol *sym)
* Result: Pointer to the newly defined symbol. If we are in the top-level
* scope, it's the same @sym as passed to the function.
*/
#define cf_define_symbol(osym_, type_, var_, def_) ({ \
struct symbol *sym_ = cf_localize_symbol(osym_); \
#define cf_define_symbol(conf_, osym_, type_, var_, def_) ({ \
struct symbol *sym_ = cf_localize_symbol(conf_, osym_); \
sym_->class = type_; \
sym_->var_ = def_; \
sym_; })
void cf_push_scope(struct symbol *);
void cf_pop_scope(void);
void cf_push_soft_scope(void);
void cf_pop_soft_scope(void);
void cf_push_scope(struct config *, struct symbol *);
void cf_pop_scope(struct config *);
void cf_push_soft_scope(struct config *);
void cf_pop_soft_scope(struct config *);
static inline void cf_push_block_scope(void)
{ cf_push_scope(NULL); conf_this_scope->block = 1; }
static inline void cf_push_block_scope(struct config *conf)
{ cf_push_scope(conf, NULL); conf->current_scope->block = 1; }
static inline void cf_pop_block_scope(void)
{ ASSERT(conf_this_scope->block); cf_pop_scope(); }
static inline void cf_pop_block_scope(struct config *conf)
{ ASSERT(conf->current_scope->block); cf_pop_scope(conf); }
char *cf_symbol_class_name(struct symbol *sym);

View File

@ -19,7 +19,7 @@ CF_HDR
#include "lib/string.h"
#include "nest/protocol.h"
#include "nest/iface.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/bfd.h"
#include "nest/cli.h"
#include "filter/filter.h"
@ -97,19 +97,20 @@ CF_DECLS
struct timeformat *tf;
struct settle_config settle;
struct adata *ad;
struct bytestring *bs;
const struct adata *bs;
struct aggr_item_node *ai;
}
%token END CLI_MARKER INVALID_TOKEN ELSECOL DDOT
%token GEQ LEQ NEQ AND OR
%token GEQ LEQ NEQ AND OR IMP
%token PO PC
%token <i> NUM ENUM
%token <ip4> IP4
%token <ip6> IP6
%token <i64> VPN_RD
%token <s> CF_SYM_KNOWN CF_SYM_UNDEFINED
%token <s> CF_SYM_KNOWN CF_SYM_UNDEFINED CF_SYM_METHOD_BARE CF_SYM_METHOD_ARGS
%token <t> TEXT
%token <bs> BYTESTRING
%token <bs> BYTETEXT
%type <iface> ipa_scope
%type <i> expr bool pxlen4
@ -121,11 +122,15 @@ CF_DECLS
%type <ad> label_stack_start label_stack
%type <t> text opttext
%type <s> symbol symbol_known toksym
%type <bs> bytestring
%type <s> symbol
%type <v> bytestring_text text_or_ipa
%type <x> bytestring_expr
%nonassoc PREFIX_DUMMY
%left AND OR
%nonassoc '=' '<' '>' '~' GEQ LEQ NEQ NMA PO PC
%nonassoc '=' '<' '>' '~' GEQ LEQ NEQ NMA IMP PO PC
%left '|' '&'
%left '+' '-'
%left '*' '/' '%'
@ -134,7 +139,7 @@ CF_DECLS
%start config
CF_KEYWORDS(DEFINE, ON, OFF, YES, NO, S, MS, US, PORT, VPN, MPLS, FROM)
CF_KEYWORDS(DEFINE, ON, OFF, YES, NO, S, MS, US, PORT, VPN, MPLS, FROM, MAX, AS)
CF_GRAMMAR
@ -158,16 +163,15 @@ conf: definition ;
definition:
DEFINE symbol '=' term ';' {
struct f_val val;
if (f_eval(f_linearize($4, 1), &val) > F_RETURN) cf_error("Runtime error");
cf_define_symbol($2, SYM_CONSTANT | val.type, val, lp_val_copy(cfg_mem, &val));
struct f_val *val = cf_eval($4, T_VOID);
cf_define_symbol(new_config, $2, SYM_CONSTANT | val->type, val, val);
}
;
expr:
NUM
| '(' term ')' { $$ = f_eval_int(f_linearize($2, 1)); }
| symbol_known {
| '(' term ')' { $$ = cf_eval_int($2); }
| CF_SYM_KNOWN {
if ($1->class != (SYM_CONSTANT | T_INT)) cf_error("Number constant expected");
$$ = SYM_VAL($1).i; }
;
@ -178,9 +182,7 @@ expr_us:
| expr US { $$ = $1 US_; }
;
toksym: FROM | PREFERENCE ;
symbol: CF_SYM_UNDEFINED | CF_SYM_KNOWN | toksym ;
symbol_known: CF_SYM_KNOWN | toksym ;
symbol: CF_SYM_UNDEFINED | CF_SYM_KNOWN ;
/* Switches */
@ -410,6 +412,45 @@ opttext:
| /* empty */ { $$ = NULL; }
;
text_or_ipa:
TEXT { $$.type = T_STRING; $$.val.s = $1; }
| IP4 { $$.type = T_IP; $$.val.ip = ipa_from_ip4($1); }
| IP6 { $$.type = T_IP; $$.val.ip = ipa_from_ip6($1); }
| CF_SYM_KNOWN {
if (($1->class == (SYM_CONSTANT | T_STRING)) ||
($1->class == (SYM_CONSTANT | T_IP)))
$$ = *($1->val);
else
cf_error("String or IP constant expected");
}
| '(' term ')' {
$$ = *cf_eval($2, T_VOID);
if (($$.type != T_BYTESTRING) && ($$.type != T_STRING))
cf_error("Bytestring or string value expected");
}
;
bytestring:
BYTETEXT
| bytestring_expr { $$ = cf_eval($1, T_BYTESTRING)->val.bs; }
;
bytestring_text:
BYTETEXT { $$.type = T_BYTESTRING; $$.val.bs = $1; }
| TEXT { $$.type = T_STRING; $$.val.s = $1; }
| bytestring_expr {
$$ = *cf_eval($1, T_VOID);
if (($$.type != T_BYTESTRING) && ($$.type != T_STRING))
cf_error("Bytestring or string value expected");
}
;
bytestring_expr:
lvalue { $$ = f_lval_getter(&$1); }
| term_bs
| '(' term ')' { $$ = $2; }
;
CF_CODE

View File

@ -23,7 +23,7 @@ m4_define(CF_DECLS, `m4_divert(-1)')
m4_define(CF_DEFINES, `m4_divert(-1)')
# Keywords are translated to C initializers
m4_define(CF_handle_kw, `m4_divert(1){ "m4_translit($1,[[A-Z]],[[a-z]])", $1, NULL },
m4_define(CF_handle_kw, `m4_divert(1){ "m4_translit($1,[[A-Z]],[[a-z]])", $1 },
m4_divert(-1)')
m4_define(CF_keywd, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)CF_handle_kw($1)]])')
m4_define(CF_KEYWORDS, `CF_iterate([[CF_keywd]], [[$@]])DNL')
@ -34,16 +34,16 @@ m4_define(CF_CLI, `CF_KEYWORDS(m4_translit($1, [[ ]], [[,]]))
# Enums are translated to C initializers: use CF_ENUM(typename, prefix, values)
# For different prefix: CF_ENUM_PX(typename, external prefix, C prefix, values)
m4_define(CF_enum, `m4_divert(1){ "CF_enum_prefix_ext[[]]$1", -((CF_enum_type<<16) | CF_enum_prefix_int[[]]$1), NULL },
m4_define(CF_enum, `m4_divert(1){ "CF_enum_prefix_ext[[]]$1", -((CF_enum_type<<16) | CF_enum_prefix_int[[]]$1) },
m4_divert(-1)')
m4_define(CF_ENUM, `m4_define([[CF_enum_type]],$1)m4_define([[CF_enum_prefix_ext]],$2)m4_define([[CF_enum_prefix_int]],$2)CF_iterate([[CF_enum]], [[m4_shift(m4_shift($@))]])DNL')
m4_define(CF_ENUM_PX, `m4_define([[CF_enum_type]],$1)m4_define([[CF_enum_prefix_ext]],$2)m4_define([[CF_enum_prefix_int]],$3)CF_iterate([[CF_enum]], [[m4_shift(m4_shift(m4_shift($@)))]])DNL')
# After all configuration templates end, we generate the
# After all configuration templates end, we generate the keyword list
m4_m4wrap(`
m4_divert(0)
static struct keyword keyword_list[] = {
m4_undivert(1){ NULL, -1, NULL } };
static const struct keyword keyword_list[] = {
m4_undivert(1){ NULL, -1 } };
')
# As we are processing C source, we must access all M4 primitives via

View File

@ -33,6 +33,8 @@ m4_define(CF_iterate, `m4_define([[CF_iter]], m4_defn([[$1]]))CF_itera($2)')
m4_define(CF_keywd, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)m4_define([[CF_toks]],CF_toks $1)]])')
m4_define(CF_KEYWORDS, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd]], [[$@]])m4_ifelse(CF_toks,,,%token<s>[[]]CF_toks
)DNL')
m4_define(CF_METHODS, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd]], [[$@]])m4_ifelse(CF_toks,,,%token<s>[[]]CF_toks
)DNL')
# CLI commands
m4_define(CF_CLI, `m4_define([[CF_cmd]], cmd_[[]]m4_translit($1, [[ ]], _))DNL

View File

@ -307,16 +307,18 @@ if test "$enable_mpls_kernel" != no ; then
fi
# temporarily removed "mrt" from all_protocols to speed up 3.0-alpha1 release
all_protocols="bfd babel bgp ospf perf pipe radv rip rpki static"
all_protocols="aggregator bfd babel bgp ospf perf pipe radv rip rpki static"
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
if test "$with_protocols" = all ; then
with_protocols="$all_protocols"
fi
AH_TEMPLATE([CONFIG_AGGREGATOR],[Aggregator protocol])
AH_TEMPLATE([CONFIG_BABEL], [Babel protocol])
AH_TEMPLATE([CONFIG_BFD], [BFD protocol])
AH_TEMPLATE([CONFIG_BGP], [BGP protocol])
AH_TEMPLATE([CONFIG_BMP], [BMP protocol])
AH_TEMPLATE([CONFIG_MRT], [MRT protocol])
AH_TEMPLATE([CONFIG_OSPF], [OSPF protocol])
AH_TEMPLATE([CONFIG_PIPE], [Pipe protocol])

View File

@ -6,3 +6,7 @@ make_archive_script = "tools/make-dev-archive"
[upstream]
# needed for get-archive
archive_url = "https://bird.network.cz/download/bird-{{ version }}.tar.gz"
[apkg]
# apkg compat level
compat = 3

View File

@ -65,11 +65,23 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
<email>&dhemail;</email>
</address>
</author>
<author>
<firstname>Jakub</firstname>
<surname>Ružička</surname>
<contrib>Updated this manpage for birdcl.</contrib>
<address>
<email>jakub.ruzicka@nic.cz</email>
</address>
</author>
</authorgroup>
<copyright>
<year>2010</year>
<holder>&dhusername;</holder>
</copyright>
<copyright>
<year>2022</year>
<holder>Jakub Ružička</holder>
</copyright>
<legalnotice>
<para>This manual page was written for the Debian system
(and may be used by others).</para>
@ -95,6 +107,10 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
<refname>birdc</refname>
<refpurpose>BIRD Internet Routing Daemon remote control</refpurpose>
</refnamediv>
<refnamediv>
<refname>birdcl</refname>
<refpurpose>BIRD Internet Routing Daemon remote control light</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
@ -118,6 +134,13 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
<arg choice="opt"><option>-s <replaceable>control-socket</replaceable></option></arg>
<arg choice="opt"><option>-v</option></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>birdcl</command>
<arg choice="opt"><option>-l</option></arg>
<arg choice="opt"><option>-r</option></arg>
<arg choice="opt"><option>-s <replaceable>control-socket</replaceable></option></arg>
<arg choice="opt"><option>-v</option></arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1 id="description">
@ -136,7 +159,10 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
communicate. Once started, <command>bird</command> will give access
to an interactive shell: commands can be completed with TAB and help
can be requested by pressing the key `?'. More documentation on
the available commands can be foung on the website, see below.</para>
the available commands can be found on the website, see below.</para>
<para><command>birdcl</command> is a light version of <command>birdc</command>
remote control for <command>bird</command> without readline/ncurses support.
TAB completion isn't available.</para>
</refsect1>
<refsect1 id="options">
@ -230,7 +256,7 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
</varlistentry>
</variablelist>
<para>The <command>birdc</command> accepts these options:</para>
<para><command>birdc</command> and <command>birdcl</command> accept these options:</para>
<variablelist>
<varlistentry>
<term><option>-l</option></term>
@ -279,8 +305,7 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
<refsect1 id="see_also">
<title>SEE ALSO</title>
<para>More documentation con be found on the website:
<para>More documentation can be found on the website:
https://bird.network.cz/.</para>
</refsect1>
</refentry>

View File

@ -1 +0,0 @@
bird2: binary-without-manpage usr/sbin/birdcl

View File

@ -1,2 +1,3 @@
bird.8
birdc.8
birdcl.8

View File

@ -2,4 +2,4 @@ bird2 ({{ version }}-cznic.{{ release }}) unstable; urgency=medium
* upstream package
-- Jakub Ružička <jakub.ruzicka@nic.cz> Mon, 29 Mar 2021 14:15:50 +0000
-- Jakub Ružička <jakub.ruzicka@nic.cz> {{ now }}

View File

@ -1 +1 @@
9
11

View File

@ -5,18 +5,18 @@ Build-Depends: bison,
debhelper,
docbook-xsl,
flex,
libncurses5-dev,
libncurses-dev,
libreadline-dev | libreadline6-dev | libreadline5-dev,
libssh-gcrypt-dev,
linuxdoc-tools-latex,
m4,
opensp,
quilt,
texlive-latex-extra,
xsltproc
Build-Depends-Indep: linuxdoc-tools-latex,
opensp,
texlive-latex-extra
Maintainer: Jakub Ružička <jakub.ruzicka@nic.cz>
Uploaders: Ondřej Surý <ondrej@debian.org>
Standards-Version: 4.3.0
Standards-Version: 4.6.2
Vcs-Browser: https://salsa.debian.org/debian/bird2
Vcs-Git: https://salsa.debian.org/debian/bird2.git
Homepage: https://bird.network.cz/
@ -26,7 +26,6 @@ Architecture: kfreebsd-any linux-any
Pre-Depends: init-system-helpers,
${misc:Pre-Depends}
Depends: adduser,
lsb-base,
ucf,
${misc:Depends},
${shlibs:Depends}

View File

@ -23,8 +23,7 @@ LDFLAGS += -g -O2 -fno-strict-aliasing -fno-strict-overflow -fPIC -Wl,-z,defs -W
override_dh_auto_configure:
CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" dh_auto_configure -- $(COMMON_FLAGS) --with-protocols=all
override_dh_auto_build:
dh_auto_build
override_dh_auto_build-indep:
dh_auto_build -- docs
override_dh_auto_install:
@ -47,10 +46,10 @@ override_dh_installman: bird.8
override_dh_clean:
dh_clean
-rm -f bird.8 birdc.8
-rm -f bird.8 birdc.8 birdcl.8
override_dh_missing:
dh_missing --fail-missing
dh_missing
override_dh_compress:
dh_compress -X.conf

View File

@ -1,2 +1,2 @@
version=3
version=4
https://bird.network.cz/download/bird-(2\.[\d.]+).tar.gz

View File

@ -1,17 +1,21 @@
%global _hardened_build 1
%global _without_doc 1
%{!?_rundir:%global _rundir %%{_localstatedir}/run}
Name: bird
Version: {{ version }}
Release: cznic.{{ release }}%{?dist}
Summary: BIRD Internet Routing Daemon
Group: System Environment/Daemons
License: GPL-2.0-or-later
URL: https://bird.network.cz/
Source0: https://bird.network.cz/download/bird-%{version}.tar.gz
Source1: bird.service
Source2: bird.tmpfilesd
Source3: bird.sysusersd
BuildRequires: autoconf
BuildRequires: flex
BuildRequires: bison
BuildRequires: ncurses-devel
@ -20,14 +24,13 @@ BuildRequires: sed
BuildRequires: gcc
BuildRequires: make
BuildRequires: libssh-devel
%if 0%{?fedora} || (0%{?rhel} && 0%{?rhel} > 7)
BuildRequires: systemd-rpm-macros
%else
BuildRequires: systemd
%if 0%{?rhel} && 0%{?rhel} < 8
# http://trubka.network.cz/pipermail/bird-users/2019-August/013631.html
BuildRequires: devtoolset-8-toolchain
%endif
Obsoletes: bird6 < 2.0.2-1
Provides: bird6 = %{version}-%{release}
BuildRequires: systemd-rpm-macros
%{?systemd_requires}
%{?sysusers_requires_compat}
%description
BIRD is a dynamic IP routing daemon supporting both, IPv4 and IPv6, Border
@ -41,6 +44,7 @@ powerful language for route filtering.
%if 0%{!?_without_doc:1}
%package doc
Summary: Documentation for BIRD Internet Routing Daemon
Group: Documentation
BuildRequires: linuxdoc-tools sgml-common perl(FindBin)
BuildArch: noarch
@ -57,9 +61,13 @@ powerful language for route filtering.
%endif
%prep
%setup -q
%setup -q -n bird-%{version}
%build
%if 0%{?rhel} && 0%{?rhel} < 8
. /opt/rh/devtoolset-8/enable
%endif
%configure --runstatedir=%{_rundir}/bird
%make_build all %{!?_without_doc:docs}
@ -70,17 +78,18 @@ powerful language for route filtering.
install -d %{buildroot}{%{_localstatedir}/lib/bird,%{_rundir}/bird}
install -D -p -m 0644 %{SOURCE1} %{buildroot}%{_unitdir}/bird.service
install -D -p -m 0644 %{SOURCE2} %{buildroot}%{_tmpfilesdir}/bird.conf
install -D -p -m 0644 %{SOURCE3} %{buildroot}%{_sysusersdir}/bird.conf
{% endraw %}
%check
%if 0%{?rhel} && 0%{?rhel} < 8
. /opt/rh/devtoolset-8/enable
%endif
make test
%pre
getent group bird >/dev/null || groupadd -r bird
getent passwd bird >/dev/null || \
useradd -r -g bird -d %{_localstatedir}/lib/bird -s /sbin/nologin \
-c "BIRD daemon user" bird
exit 0
%sysusers_create_compat %{SOURCE3}
%post
%systemd_post bird.service
@ -95,12 +104,13 @@ exit 0
%doc NEWS README
%attr(0640,root,bird) %config(noreplace) %{_sysconfdir}/bird.conf
%{_unitdir}/bird.service
%{_sysusersdir}/bird.conf
%{_tmpfilesdir}/bird.conf
%{_sbindir}/bird
%{_sbindir}/birdc
%{_sbindir}/birdcl
%dir %attr(0750,bird,bird) %{_localstatedir}/lib/bird
%dir %attr(0750,bird,bird) %ghost %{_rundir}/bird
%dir %attr(0750,bird,bird) %{_rundir}/bird
%if 0%{!?_without_doc:1}
%files doc

View File

@ -0,0 +1,2 @@
#Type Name ID GECOS Home directory Shell
u bird - "BIRD daemon user" /var/lib/bird -

3
distro/tests/control Normal file
View File

@ -0,0 +1,3 @@
Tests: test-bird.sh
Restrictions: needs-root
Depends: bird2, iproute2

105
distro/tests/test-bird.sh Executable file
View File

@ -0,0 +1,105 @@
#!/bin/bash
set -e
LOCAL=2001:db8:dead::
EXTERNAL=2001:db8:beef::
EXTERNAL_NET=${EXTERNAL}/48
EXTERNAL_NH=${LOCAL}beef
LEARN=2001:db8:feed::
LEARN_NET=${LEARN}/48
LEARN_NH=${LOCAL}feed
IFACE=bird-test-dummy
IFACE_EXISTS=false
BIRD_RUNNING=false
D=$(mktemp -d)
pushd ${D} >/dev/null
stop_bird() {
birdc -l down >/dev/null
sleep 1
grep -q "<FATAL> Shutdown completed" bird.log
[ ! -e bird.pid ]
[ ! -e bird.ctl ]
}
cleanup() {
if ${BIRD_RUNNING}; then
stop_bird
if [ -e bird.pid ]; then
kill -9 $(<bird.pid)
fi
fi
if ${IFACE_EXISTS}; then
ip link del ${IFACE}
fi
popd > /dev/null
rm -rf ${D}
}
failed() {
cleanup
exit 1
}
trap failed ERR
trap failed INT
trap failed HUP
ip link add ${IFACE} type dummy
IFACE_EXISTS=true
ip link set ${IFACE} up
ip -6 addr add ${LOCAL}/64 dev bird-test-dummy
ip -6 route add ${LEARN_NET} via ${LEARN_NH}
cat >bird.conf <<EOF
log "bird.log" all;
protocol device {}
protocol kernel {
ipv6 { import all; export all; };
learn;
}
protocol static {
ipv6;
route ${EXTERNAL_NET} via ${EXTERNAL_NH};
}
EOF
bird -l -P bird.pid
if [ ! -S bird.ctl ] || [ ! -f bird.pid ] || [ ! -f bird.log ]; then
failed
fi
BIRD_RUNNING=true
ROUTE_INSERTED=false
for _ in $(seq 10); do
if ip -6 route show ${EXTERNAL_NET} | egrep -q "${EXTERNAL_NET} via ${EXTERNAL_NH} dev ${IFACE} proto bird metric [0-9]+ pref medium"; then
ROUTE_INSERTED=true
break
fi
sleep 1
done
$ROUTE_INSERTED || failed
if birdc -l show route "${LEARN_NET}" | egrep -q "Network not found"; then
failed
fi
cleanup
exit 0

View File

@ -551,7 +551,7 @@ include "tablename.conf";;
Define a filter. You can learn more about filters in the following
chapter.
<tag><label id="opt-function">function <m/name/ (<m/parameters/) <m/local variables/ { <m/commands/ }</tag>
<tag><label id="opt-function">function <m/name/ (<m/parameters/) [ -&gt; <m/return type/ ] <m/local variables/ { <m/commands/ }</tag>
Define a function. You can learn more about functions in the following chapter.
<tag><label id="opt-protocol">protocol rip|ospf|bgp|<m/.../ [<m/name/ [from <m/name2/]] { <m>protocol options</m> }</tag>
@ -869,7 +869,7 @@ agreement").
protocol packets are processed in the local TX queues. This option is
Linux specific. Default value is 7 (highest priority, privileged traffic).
<tag><label id="proto-pass">password "<m/password/" | <m/hex_key/ [ { <m>password options</m> } ] </tag>
<tag><label id="proto-pass">password "<m/password/" | <m/bytestring/ [ { <m>password options</m> } ] </tag>
Specifies a password that can be used by the protocol as a shared secret
key. Password option can be used more times to specify more passwords.
If more passwords are specified, it is a protocol-dependent decision
@ -877,10 +877,8 @@ agreement").
authentication is enabled, authentication can be enabled by separate,
protocol-dependent <cf/authentication/ option.
A password can also be specified as a hexadecimal key. <m/hex_key/ is a
sequence of hexadecimal digit pairs, optionally colon-separated. A key
specified this way must be at least 16 bytes (32 digits) long (although
specific algorithms can impose other restrictions).
A password can be specified as a string or as a sequence of hexadecimal
digit pairs (<ref id="type-bytestring" name="bytestring">).
This option is allowed in BFD, OSPF, RIP, and Babel protocols. BGP has
also <cf/password/ option, but it is slightly different and described
@ -1350,20 +1348,24 @@ can group several statements to a single compound statement by using braces
(<cf>{ <M>statements</M> }</cf>) which is useful if you want to make a bigger
block of code conditional.
<p>BIRD supports functions, so that you don't have to repeat the same blocks of
code over and over. Functions can have zero or more parameters and they can have
local variables. Recursion is not allowed. Function definitions look like this:
<p>BIRD supports functions, so that you don not have to repeat the same blocks
of code over and over. Functions can have zero or more parameters and they can
have local variables. If the function returns value, then you should always
specify its return type. Direct recursion is possible. Function definitions look
like this:
<code>
function name ()
function name() -> int
{
int local_variable;
int another_variable = 5;
return 42;
}
function with_parameters (int parameter)
function with_parameters(int parameter) -> pair
{
print parameter;
return (1, 2);
}
</code>
@ -1377,7 +1379,7 @@ may return values using the <cf>return <m/[expr]/</cf> command. Returning a
value exits from current function (this is similar to C).
<p>Filters are defined in a way similar to functions except they cannot have
explicit parameters. They get a route table entry as an implicit parameter, it
explicit parameters and cannot return. They get a route table entry as an implicit parameter, it
is also passed automatically to any functions called. The filter must terminate
with either <cf/accept/ or <cf/reject/ statement. If there is a runtime error in
filter, the route is rejected.
@ -1441,6 +1443,27 @@ in the foot).
!&tilde;/) operators could be used to match a string value against
a shell pattern (represented also as a string).
<tag><label id="type-bytestring">bytestring</tag>
This is a sequences of arbitrary bytes. There are no ways to modify
bytestrings in filters. You can pass them between function, assign
them to variables of type <cf/bytestring/, print such values,
compare bytestings (<cf/=, !=/). Bytestring literals are written
in several ways:
A sequence of hexadecimal digit pairs, optionally colon-separated.
A bytestring specified this way must be at least 16 bytes (32 digits)
long: <cf/01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef/ or
<cf/0123456789abcdef0123456789abcdef/.
A sequence of hexadecimal digit pairs of any lengh (including zero)
with the <cf/hex:/ prefix. Colon separators can be inserted
arbitrarily between any bytes: <cf/hex:/, <cf/hex:1234/,
<cf/hex:1234:56:78/.
A bytestring can be made from a hex string using <cf/from_hex()/
function. Source strings can use any number of colons, hyphens and
spaces as byte separators: <cf/from_hex(" 1234 56:78 ab-cd-ef ")/.
<tag><label id="type-ip">ip</tag>
This type can hold a single IP address. The IPv4 addresses are stored as
IPv4-Mapped IPv6 addresses so one data type for both of them is used.
@ -1623,23 +1646,24 @@ in the foot).
<cf><m/P/.len</cf> returns the length of path <m/P/.
<cf><m/P/.empty</cf> makes the path <m/P/ empty.
<cf><m/P/.empty</cf> makes the path <m/P/ empty. Can't be used as a value, always modifies the object.
<cf>prepend(<m/P/,<m/A/)</cf> prepends ASN <m/A/ to path <m/P/ and
<cf><m/P/.prepend(<m/A/)</cf> prepends ASN <m/A/ to path <m/P/ and
returns the result.
<cf>delete(<m/P/,<m/A/)</cf> deletes all instances of ASN <m/A/ from
<cf><m/P/.delete(<m/A/)</cf> deletes all instances of ASN <m/A/ from
from path <m/P/ and returns the result. <m/A/ may also be an integer
set, in that case the operator deletes all ASNs from path <m/P/ that are
also members of set <m/A/.
<cf>filter(<m/P/,<m/A/)</cf> deletes all ASNs from path <m/P/ that are
not members of integer set <m/A/. I.e., <cf/filter/ do the same as
<cf/delete/ with inverted set <m/A/.
<cf><m/P/.filter(<m/A/)</cf> deletes all ASNs from path <m/P/ that are
not members of integer set <m/A/, and returns the result.
I.e., <cf/filter/ do the same as <cf/delete/ with inverted set <m/A/.
Statement <cf><m/P/ = prepend(<m/P/, <m/A/);</cf> can be shortened to
<cf><m/P/.prepend(<m/A/);</cf> if <m/P/ is appropriate route attribute
(for example <cf/bgp_path/). Similarly for <cf/delete/ and <cf/filter/.
Methods <cf>prepend</cf>, <cf>delete</cf> and <cf>filter</cf> keep the
original object intact as long as you use the result in any way. You can
also write e.g. <cf><m/P/.prepend(<m/A/);</cf> as a standalone statement.
This variant does modify the original object with the result of the operation.
<tag><label id="type-bgpmask">bgpmask</tag>
BGP masks are patterns used for BGP path matching (using <cf>path
@ -1667,28 +1691,29 @@ in the foot).
<cf><m/C/.len</cf> returns the length of clist <m/C/.
<cf><m/C/.empty</cf> makes the list <m/C/ empty.
<cf><m/C/.empty</cf> makes the list <m/C/ empty. Can't be used as a value, always modifies the object.
<cf>add(<m/C/,<m/P/)</cf> adds pair (or quad) <m/P/ to clist <m/C/ and
<cf><m/C/.add(<m/P/)</cf> adds pair (or quad) <m/P/ to clist <m/C/ and
returns the result. If item <m/P/ is already in clist <m/C/, it does
nothing. <m/P/ may also be a clist, in that case all its members are
added; i.e., it works as clist union.
<cf>delete(<m/C/,<m/P/)</cf> deletes pair (or quad) <m/P/ from clist
<cf><m/C/.delete(<m/P/)</cf> deletes pair (or quad) <m/P/ from clist
<m/C/ and returns the result. If clist <m/C/ does not contain item
<m/P/, it does nothing. <m/P/ may also be a pair (or quad) set, in that
case the operator deletes all items from clist <m/C/ that are also
members of set <m/P/. Moreover, <m/P/ may also be a clist, which works
analogously; i.e., it works as clist difference.
<cf>filter(<m/C/,<m/P/)</cf> deletes all items from clist <m/C/ that are
not members of pair (or quad) set <m/P/. I.e., <cf/filter/ do the same
<cf><m/C/.filter(<m/P/)</cf> deletes all items from clist <m/C/ that are
not members of pair (or quad) set <m/P/, and returns the result. I.e., <cf/filter/ do the same
as <cf/delete/ with inverted set <m/P/. <m/P/ may also be a clist, which
works analogously; i.e., it works as clist intersection.
Statement <cf><m/C/ = add(<m/C/, <m/P/);</cf> can be shortened to
<cf><m/C/.add(<m/P/);</cf> if <m/C/ is appropriate route attribute (for
example <cf/bgp_community/). Similarly for <cf/delete/ and <cf/filter/.
Methods <cf>add</cf>, <cf>delete</cf> and <cf>filter</cf> keep the
original object intact as long as you use the result in any way. You can
also write e.g. <cf><m/P/.add(<m/A/);</cf> as a standalone statement.
This variant does modify the original object with the result of the operation.
<cf><m/C/.min</cf> returns the minimum element of clist <m/C/.
@ -1917,6 +1942,70 @@ protocol sections.
<chapt>Protocols
<label id="protocols">
<sect>Aggregator
<label id="aggregator">
<sect1>Introduction
<label id="aggregator-intro">
<p>The Aggregator protocol explicitly merges routes by the given rules. There
are four phases of aggregation. First routes are filtered, then sorted into buckets,
then buckets are merged and finally the results are filtered once again.
Aggregating an already aggregated route is forbidden.
<p>This is an experimental protocol, use with caution.
<sect1>Configuration
<label id="aggregator-config">
<p><descrip>
<tag><label id="aggregator-table">table <m/table/</tag>
The table from which routes are exported to get aggregated.
<tag><label id="aggregator-export">export <m/.../</tag>
A standard channel's <cf/export/ clause, defining which routes are accepted into aggregation.
<tag><label id="aggregator-rule">aggregate on <m/expr/ | <m/attribute/ [<m/, .../]</tag>
All the given filter expressions and route attributes are evaluated for each route. Then routes
are sorted into buckets where <em/all/ values are the same. Note: due to performance reasons,
all filter expressions must return a compact type, e.g. integer, a BGP
(standard, extended, large) community or an IP address. If you need to compare e.g. modified
AS Paths in the aggregation rule, you can define a custom route attribute and set this attribute
in the export filter. For now, it's mandatory to say <cf/net/ here, we can't merge prefixes yet.
<tag><label id="aggregation-merge">merge by { <m/filter code/ }</tag>
The given filter code has an extra symbol defined: <cf/routes/. By iterating over <cf/routes/,
you get all the routes in the bucket and you can construct your new route. All attributes
selected in <cf/aggregate on/ are already set to the common values. For now, it's not possible
to use a named filter here. You have to finalize the route by calling <cf/accept/.
<tag><label id="aggregator-import">import <m/.../</tag>
Filter applied to the route after <cf/merge by/. Here you can use a named filter.
<tag><label id="aggregator-peer-table">peer table <m/table/</tag>
The table to which aggregated routes are imported. It may be the same table
as <cf/table/.
</descrip>
<sect1>Example
<label id="aggregator-example">
<p><code>
protocol aggregator {
table master6;
export where defined(bgp_path);
/* Merge all routes with the same AS Path length */
aggregate on net, bgp_path.len;
merge by {
for route r in routes do {
if ! defined(bgp_path) then { bgp_path = r.bgp_path }
bgp_community = bgp_community.add(r.bgp_community);
}
accept;
};
import all;
peer table agr_result;
}
</code>
<sect>Babel
<label id="babel">
@ -1955,7 +2044,7 @@ protocol babel [<name>] {
ipv6 [sadr] { <channel config> };
randomize router id <switch>;
interface <interface pattern> {
type <wired|wireless>;
type <wired|wireless|tunnel>;
rxcost <number>;
limit <number>;
hello interval <time>;
@ -1969,6 +2058,11 @@ protocol babel [<name>] {
next hop ipv4 <address>;
next hop ipv6 <address>;
extended next hop <switch>;
rtt cost <number>;
rtt min <time>;
rtt max <time>;
rtt decay <number>;
send timestamps <switch>;
authentication none|mac [permissive];
password "&lt;text&gt;";
password "&lt;text&gt;" {
@ -1999,15 +2093,16 @@ protocol babel [<name>] {
router ID every time it starts up, which avoids this problem at the cost
of not having stable router IDs in the network. Default: no.
<tag><label id="babel-type">type wired|wireless </tag>
This option specifies the interface type: Wired or wireless. On wired
interfaces a neighbor is considered unreachable after a small number of
Hello packets are lost, as described by <cf/limit/ option. On wireless
<tag><label id="babel-type">type wired|wireless|tunnel </tag>
This option specifies the interface type: Wired, wireless or tunnel. On
wired interfaces a neighbor is considered unreachable after a small number
of Hello packets are lost, as described by <cf/limit/ option. On wireless
interfaces the ETX link quality estimation technique is used to compute
the metrics of routes discovered over this interface. This technique will
gradually degrade the metric of routes when packets are lost rather than
the more binary up/down mechanism of wired type links. Default:
<cf/wired/.
the more binary up/down mechanism of wired type links. A tunnel is like a
wired interface, but turns on RTT-based metrics with a default cost of 96.
Default: <cf/wired/.
<tag><label id="babel-rxcost">rxcost <m/num/</tag>
This option specifies the nominal RX cost of the interface. The effective
@ -2078,6 +2173,37 @@ protocol babel [<name>] {
hop when IPv4 addresses are absent from the interface as described in
<rfc id="9229">. Default: yes.
<tag><label id="babel-rtt-cost">rtt cost <m/number/</tag>
The RTT-based cost that will be applied to all routes from each neighbour
based on the measured RTT to that neighbour. If this value is set,
timestamps will be included in generated Babel Hello and IHU messages, and
(if the neighbours also have timestamps enabled), the RTT to each
neighbour will be computed. An additional cost is added to a neighbour if
its RTT is above the <ref id="babel-rtt-min" name="rtt min"> value
configured on the interface. The added cost scales linearly from 0 up to
the RTT cost configured in this option; the full cost is applied if the
neighbour RTT reaches the RTT configured in the <ref id="babel-rtt-max"
name="rtt max"> option (and for all RTTs above this value). Default: 0
(disabled), except for tunnel interfaces, where it is 96.
<tag><label id="babel-rtt-min">rtt min <m/time/ s|ms</tag>
The minimum RTT above which the RTT cost will start to be applied (scaling
linearly from zero up to the full cost). Default: 10 ms
<tag><label id="babel-rtt-max">rtt max <m/time/ s|ms</tag>
The maximum RTT above which the full RTT cost will start be applied.
Default: 120 ms
<tag><label id="babel-rtt-decay">rtt decay <m/number/</tag>
The decay factor used for the exponentional moving average of the RTT
samples from each neighbour, in units of 1/256. Higher values discards old
RTT samples faster. Must be between 1 and 256. Default: 42
<tag><label id="babel-send-timestamps">send timestamps <m/switch/</tag>
Whether to send the timestamps used for RTT calculation on this interface.
Sending the timestamps enables peers to calculate an RTT to this node,
even if no RTT cost is applied to the route metrics. Default: yes.
<tag><label id="babel-authentication">authentication none|mac [permissive]</tag>
Selects authentication method to be used. <cf/none/ means that packets
are not authenticated at all, <cf/mac/ means MAC authentication is
@ -2425,7 +2551,6 @@ avoid routing loops.
<item> <rfc id="5065"> - AS confederations for BGP
<item> <rfc id="5082"> - Generalized TTL Security Mechanism
<item> <rfc id="5492"> - Capabilities Advertisement with BGP
<item> <rfc id="5549"> - Advertising IPv4 NLRI with an IPv6 Next Hop
<item> <rfc id="5575"> - Dissemination of Flow Specification Rules
<item> <rfc id="5668"> - 4-Octet AS Specific BGP Extended Community
<item> <rfc id="6286"> - AS-Wide Unique BGP Identifier
@ -2440,6 +2565,7 @@ avoid routing loops.
<item> <rfc id="8203"> - BGP Administrative Shutdown Communication
<item> <rfc id="8212"> - Default EBGP Route Propagation Behavior without Policies
<item> <rfc id="8654"> - Extended Message Support for BGP
<item> <rfc id="8950"> - Advertising IPv4 NLRI with an IPv6 Next Hop
<item> <rfc id="9072"> - Extended Optional Parameters Length for BGP OPEN Message
<item> <rfc id="9117"> - Revised Validation Procedure for BGP Flow Specifications
<item> <rfc id="9234"> - Route Leak Prevention and Detection Using Roles
@ -2710,13 +2836,20 @@ using the following configuration parameters:
changes its import filter, or if there is suspicion of inconsistency) it
is necessary to do a new complete route exchange. BGP protocol extension
Route Refresh (<rfc id="2918">) allows BGP speaker to request
re-advertisement of all routes from its neighbor. BGP protocol
extension Enhanced Route Refresh (<rfc id="7313">) specifies explicit
begin and end for such exchanges, therefore the receiver can remove
stale routes that were not advertised during the exchange. This option
specifies whether BIRD advertises these capabilities and supports
re-advertisement of all routes from its neighbor. This option
specifies whether BIRD advertises this capability and supports
related procedures. Note that even when disabled, BIRD can send route
refresh requests. Default: on.
refresh requests. Disabling Route Refresh also disables Enhanced Route Refresh.
Default: on.
<tag><label id="bgp-enable-enhanced-route-refresh">enable enhanced route refresh <m/switch/</tag>
BGP protocol extension Enhanced Route Refresh (<rfc id="7313">) specifies explicit
begin and end for Route Refresh (see previous option),
therefore the receiver can remove
stale routes that were not advertised during the exchange. This option
specifies whether BIRD advertises this capability and supports
related procedures.
Default: on.
<tag><label id="bgp-graceful-restart">graceful restart <m/switch/|aware</tag>
When a BGP speaker restarts or crashes, neighbors will discard all
@ -3093,7 +3226,7 @@ be used in explicit configuration.
associated network prefixes. This option provides an extension to use
IPv4 next hops with IPv6 prefixes and vice versa. For IPv4 / VPNv4
channels, the behavior is controlled by the Extended Next Hop Encoding
capability, as described in <rfc id="5549">. For IPv6 / VPNv6 channels,
capability, as described in <rfc id="8950">. For IPv6 / VPNv6 channels,
just IPv4-mapped IPv6 addresses are used, as described in
<rfc id="4798"> and <rfc id="4659">. Default: off.
@ -3255,6 +3388,13 @@ some of them (marked with `<tt/O/') are optional.
name="local Role"> is configured it set automatically.
</descrip>
<p>For attributes unknown by BIRD, the user can assign a name (on top level)
to an attribute by its number. This defined name can be used then to both set
(by a bytestring literal, transitive) or unset the given attribute even though
BIRD knows nothing about it:
<tt><label id="bgp-attribute-custom">attribute bgp <m/number/ bytestring <m/name/;</tt>
<sect1>Example
<label id="bgp-exam">
@ -3298,6 +3438,35 @@ protocol bgp {
</code>
<sect>BMP
<label id="bmp">
<p>The BGP Monitoring Protocol is used for monitoring BGP sessions and obtaining
routing table data. The current implementation in BIRD is a preliminary release
with a limited feature set, it will be subject to significant changes in the
future. It is not ready for production usage and therefore it is not compiled
by default and have to be enabled during installation by the configure option
<tt/--with-protocols=/.
<p>The implementation is limited to monitor protocol state changes and routes
in <ref id="bgp-import-table" name="BGP import tables"> (not regular routing
tables), therefore import table must be enabled in BGP protocols. All BGP
protocols are monitored automatically.
<sect1>Example
<label id="bmp-exam">
<p><code>
protocol bmp {
# The monitoring station to connect to
station address ip 198.51.100.10 port 1790;
# required option
monitoring rib in pre_policy;
}
</code>
<sect>Device
<label id="device">
@ -4521,6 +4690,21 @@ definitions, prefix definitions and DNS definitions:
options and there is a short variant <cf>dnssl <m/domain/</cf> that just
specifies one DNS search domain.
<tag><label id="radv-custom-option">custom option type <m/number/ value <m/bytestring/</tag>
Custom option definitions allow to define an arbitrary option to
advertise. You need to specify the option type number and the binary
payload of the option. The length field is calculated automatically.
Like <cf/rdnss/ above, multiple definitions are cumulative, they can
be used also as interface-specific options.
The following example advertises PREF64 option (<rfc id="8781">) with
prefix <cf>2001:db8:a:b::/96</cf> and the lifetime of <cf/1 hour/:
<label id="radv-custom-option-exam">
<p><code>
custom option type 38 value hex:0e10:2001:0db8:000a:000b:0000:0000;
</code>
<tag><label id="radv-trigger">trigger <m/prefix/</tag>
RAdv protocol could be configured to change its behavior based on
availability of routes. When this option is used, the protocol waits in
@ -4650,6 +4834,10 @@ definitions, prefix definitions and DNS definitions:
<tag><label id="radv-iface-dnssl-local">dnssl local <m/switch/</tag>
Use only local DNSSL definitions for this interface. See <cf/rdnss local/
option above. Default: no.
<tag><label id="radv-iface-custom-local">custom option local <m/switch/</tag>
Use only local custom option definitions for this interface. See <cf/rdnss local/
option above. Default: no.
</descrip>
<p>Prefix specific options
@ -5342,8 +5530,8 @@ as the destination becomes adjacent again.
(i.e., they can be used multiple times for a route, one time for each nexthop).
Syntactically, they are not separate options but just parts of <cf/route/
statement after each <cf/via/ statement, not separated by semicolons. E.g.,
statement <cf/route 10.0.0.0/8 via 192.0.2.1 bfd weight 1 via 192.0.2.2 weight
2;/ describes a route with two nexthops, the first nexthop has two per-nexthop
statement <cf>route 10.0.0.0/8 via 192.0.2.1 bfd weight 1 via 192.0.2.2 weight
2;</cf> describes a route with two nexthops, the first nexthop has two per-nexthop
options (<cf/bfd/ and <cf/weight 1/), the second nexthop has just <cf/weight 2/.
<descrip>

View File

@ -19,18 +19,59 @@ 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)
static struct symbol *this_function;
#define f_generate_complex_sym(fi_code, sym, arg) ({ \
if (sym->class != SYM_ATTRIBUTE) \
cf_error("Can't empty %s: not an attribute", sym->name); \
f_generate_complex(fi_code, sym->attribute, arg); \
})
static struct f_method_scope {
struct f_inst *object;
struct sym_scope *main;
struct sym_scope scope;
} f_method_scope_stack[32];
static int f_method_scope_pos = -1;
#define f_generate_complex_default(fi_code, da, arg, def) \
f_new_inst(FI_EA_SET, f_new_inst(fi_code, f_new_inst(FI_DEFAULT, f_new_inst(FI_EA_GET, da), f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_INT, .val.i = def })), arg), da)
static struct sym_scope *f_for_stored_scope;
#define FM (f_method_scope_stack[f_method_scope_pos])
static inline void f_method_call_start(struct f_inst *object)
{
if (object->type == T_VOID)
cf_error("Can't infer type to properly call a method, please assign the value to a variable");
if (++f_method_scope_pos >= (int) ARRAY_SIZE(f_method_scope_stack))
cf_error("Too many nested method calls");
struct sym_scope *scope = f_type_method_scope(object->type);
if (!scope->hash.count && !scope->next)
cf_error("No methods defined for type %s", f_type_name(object->type));
/* Replacing the current symbol scope with the appropriate method scope
for the given type. */
FM = (struct f_method_scope) {
.object = object,
.main = new_config->current_scope,
.scope = {
.next = scope->next,
.hash = scope->hash,
.block = 1,
.readonly = 1,
},
};
new_config->current_scope = &FM.scope;
}
static inline void f_method_call_args(void)
{
/* For argument parsing, we need to revert back to the standard symbol scope. */
new_config->current_scope = FM.main;
}
static inline void f_method_call_end(void)
{
ASSERT_DIE(f_method_scope_pos >= 0);
if (&FM.scope == new_config->current_scope)
new_config->current_scope = FM.main;
f_method_scope_pos--;
}
static int
f_new_var(struct sym_scope *s)
@ -62,6 +103,10 @@ f_new_var(struct sym_scope *s)
return offset;
}
/* Macro for top-level pre-defined variables. */
#define f_predefined_variable(conf_, name_, type_) \
cf_define_symbol(conf_, cf_get_symbol(conf_, name_), SYM_VARIABLE | type_, offset, f_new_var(conf_->current_scope))
/*
* Sets and their items are during parsing handled as lists, linked
* through left ptr. The first item in a list also contains a pointer
@ -200,34 +245,6 @@ f_new_lc_item(u32 f1, u32 t1, u32 f2, u32 t2, u32 f3, u32 t3)
return t;
}
static inline struct f_inst *
f_generate_empty(const struct symbol *sym)
{
if (sym->class != SYM_ATTRIBUTE)
cf_error("Can't empty %s: not an attribute", sym->name);
const struct ea_class *def = sym->attribute;
const struct f_val *empty = f_get_empty(def->type);
if (!empty)
cf_error("Can't empty attribute %s", def->name);
return f_new_inst(FI_EA_SET, f_new_inst(FI_CONSTANT, *empty), def);
}
static inline struct f_inst *
f_implicit_roa_check(struct rtable_config *tab)
{
const struct ea_class *def = ea_class_find("bgp_path");
if (!def)
cf_error("Fatal: Couldn't find BGP path attribute definition.");
struct f_static_attr fsa = f_new_static_attr(T_NET, SA_NET, 1);
return f_new_inst(FI_ROA_CHECK,
f_new_inst(FI_RTA_GET, fsa),
f_new_inst(FI_AS_PATH_LAST, f_new_inst(FI_EA_GET, def)),
tab);
}
/*
* Remove all new lines and doubled whitespaces
@ -283,28 +300,60 @@ assert_done(struct f_inst *expr, const char *start, const char *end)
: "???");
}
static struct f_inst *
f_lval_getter(struct f_lval *lval)
{
switch (lval->type) {
case F_LVAL_CONSTANT: return f_new_inst(FI_CONSTANT, *(lval->sym->val));
case F_LVAL_VARIABLE: return f_new_inst(FI_VAR_GET, lval->sym);
case F_LVAL_SA: return f_new_inst(FI_RTA_GET, lval->rte, lval->sa);
case F_LVAL_EA: return f_new_inst(FI_EA_GET, lval->rte, lval->da);
case F_LVAL_ATTR_BIT:
{
struct f_inst *c = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_INT, .val.i = (1U << lval->fab.bit)});
return f_new_inst(FI_EQ, c, f_new_inst(FI_BITAND, f_new_inst(FI_EA_GET, lval->rte, lval->fab.class), c));
}
default: bug("Unknown lval type");
}
}
static struct f_inst *
f_lval_setter(struct f_lval *lval, struct f_inst *expr)
{
switch (lval->type) {
case F_LVAL_CONSTANT: cf_error("Constant %s is read-only", lval->sym->name);
case F_LVAL_VARIABLE: return f_new_inst(FI_VAR_SET, expr, lval->sym);
case F_LVAL_SA:
if (lval->sa.readonly)
cf_error( "This static attribute is read-only.");
return f_new_inst(FI_RTA_SET, expr, lval->sa);
case F_LVAL_EA: return f_new_inst(FI_EA_SET, expr, lval->da);
case F_LVAL_ATTR_BIT: return f_new_inst(FI_CONDITION, expr,
f_new_inst(FI_EA_SET,
f_new_inst(FI_BITOR,
f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_INT, .val.i = (1U << lval->fab.bit)}),
f_new_inst(FI_EA_GET, lval->rte, lval->fab.class)
),
lval->fab.class),
f_new_inst(FI_EA_SET,
f_new_inst(FI_BITAND,
f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_INT, .val.i = ~(1U << lval->fab.bit)}),
f_new_inst(FI_EA_GET, lval->rte, lval->fab.class)
),
lval->fab.class)
);
default: bug("Unknown lval type");
}
}
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_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");
}
struct f_inst *setter = f_lval_setter(lval, expr),
*getter = f_lval_getter(lval);
checker = f_new_inst(FI_EQ, expr, getter);
struct f_inst *checker = f_new_inst(FI_EQ, expr, getter);
setter->next = checker;
return assert_done(setter, start, end);
@ -314,34 +363,31 @@ 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,
INT, BOOL, IP, PREFIX, RD, PAIR, QUAD, EC, LC,
SET, STRING, BYTESTRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST,
IF, THEN, ELSE, CASE,
FOR, IN, DO,
TRUE, FALSE, RT, RO, UNKNOWN, GENERIC,
FROM, GW, NET, MASK, PROTO, SCOPE, DEST, IFNAME, IFINDEX, WEIGHT, GW_MPLS,
ROA_CHECK, ASN, SRC, DST,
IS_V4, IS_V6,
LEN, MAXLEN,
DATA, DATA1, DATA2,
FROM, GW, NET, PROTO, SCOPE, DEST, IFNAME, IFINDEX, WEIGHT, GW_MPLS,
ROA_CHECK,
DEFINED,
ADD, DELETE, RESET,
PREPEND, FIRST, LAST, LAST_NONAGGREGATED,
MIN, MAX,
PREPEND,
EMPTY,
FILTER, WHERE, EVAL, ATTRIBUTE,
FROM_HEX,
BT_ASSERT, BT_TEST_SUITE, BT_CHECK_ASSIGN, BT_TEST_SAME, FORMAT, STACKS)
%nonassoc THEN
%nonassoc ELSE
%type <xp> cmds_int cmd_prep
%type <x> term cmd cmd_var cmds cmds_scoped constant constructor print_list var var_init var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail
%type <x> term term_bs cmd cmd_var cmds cmds_scoped constant constructor var var_list var_list_r function_call bgp_path_expr bgp_path bgp_path_tail term_dot_method method_name_cont
%type <fsa> static_attr
%type <f> filter where_filter
%type <fl> filter_body function_body
%type <flv> lvalue
%type <i> type function_vars
%type <i> type function_vars function_type
%type <fa> function_argsn function_args
%type <ecs> ec_kind
%type <fret> break_command
@ -363,36 +409,45 @@ conf: FILTER STACKS expr expr ';' {
conf: filter_def ;
filter_def:
FILTER symbol { $2 = cf_define_symbol($2, SYM_FILTER, filter, NULL); cf_push_scope( $2 ); }
filter_body {
FILTER symbol {
$2 = cf_define_symbol(new_config, $2, SYM_FILTER, filter, NULL);
cf_enter_filters();
cf_push_scope( new_config, $2 );
this_function = NULL;
} filter_body {
struct filter *f = cfg_alloc(sizeof(struct filter));
*f = (struct filter) { .sym = $2, .root = $4 };
$2->filter = f;
cf_pop_scope();
cf_pop_scope(new_config);
cf_exit_filters();
}
;
conf: filter_eval ;
filter_eval:
EVAL term { f_eval_int(f_linearize($2, 1)); }
EVAL term { cf_eval_int($2); }
;
conf: custom_attr ;
custom_attr: ATTRIBUTE type symbol ';' {
if (($3->class == SYM_ATTRIBUTE) && ($3->scope == new_config->root_scope))
cf_error("Duplicate attribute %s definition", $3->name);
cf_define_symbol($3, SYM_ATTRIBUTE, attribute,
ea_register_alloc(new_config->pool, (struct ea_class) {
cf_enter_filters();
struct ea_class *ac = ea_class_find_by_name($3->name);
cf_exit_filters();
if (ac && (ac->type == $2))
ea_ref_class(new_config->pool, ac);
else
ac = ea_register_alloc(new_config->pool, (struct ea_class) {
.name = $3->name,
.type = $2,
})->class);
})->class;
cf_define_symbol(new_config, $3, SYM_ATTRIBUTE, attribute, ac);
};
conf: bt_test_suite ;
bt_test_suite:
BT_TEST_SUITE '(' symbol_known ',' text ')' {
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;
@ -405,7 +460,7 @@ bt_test_suite:
conf: bt_test_same ;
bt_test_same:
BT_TEST_SAME '(' symbol_known ',' symbol_known ',' NUM ')' {
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));
@ -429,11 +484,13 @@ type:
| EC { $$ = T_EC; }
| LC { $$ = T_LC; }
| STRING { $$ = T_STRING; }
| BYTESTRING { $$ = T_BYTESTRING; }
| BGPMASK { $$ = T_PATH_MASK; }
| BGPPATH { $$ = T_PATH; }
| CLIST { $$ = T_CLIST; }
| ECLIST { $$ = T_ECLIST; }
| LCLIST { $$ = T_LCLIST; }
| ROUTE { $$ = T_ROUTE; }
| type SET {
switch ($1) {
case T_INT:
@ -461,7 +518,7 @@ function_argsn:
| function_argsn type symbol ';' {
if ($3->scope->slots >= 0xfe) cf_error("Too many declarations, at most 255 allowed");
$$ = cfg_alloc(sizeof(struct f_arg));
$$->arg = cf_define_symbol($3, SYM_VARIABLE | $2, offset, sym_->scope->slots++);
$$->arg = cf_define_symbol(new_config, $3, SYM_VARIABLE | $2, offset, sym_->scope->slots++);
$$->next = $1;
}
;
@ -470,7 +527,7 @@ function_args:
'(' ')' { $$ = NULL; }
| '(' function_argsn type symbol ')' {
$$ = cfg_alloc(sizeof(struct f_arg));
$$->arg = cf_define_symbol($4, SYM_VARIABLE | $3, offset, sym_->scope->slots++);
$$->arg = cf_define_symbol(new_config, $4, SYM_VARIABLE | $3, offset, sym_->scope->slots++);
$$->next = $2;
}
;
@ -478,31 +535,44 @@ function_args:
function_vars:
/* EMPTY */ { $$ = 0; }
| function_vars type symbol ';' {
cf_define_symbol($3, SYM_VARIABLE | $2, offset, f_new_var(sym_->scope));
cf_define_symbol(new_config, $3, SYM_VARIABLE | $2, offset, f_new_var(sym_->scope));
$$ = $1 + 1;
}
;
function_type:
/* EMPTY */ { $$ = T_VOID; }
| IMP type { $$ = $2; }
;
filter_body: function_body ;
filter:
symbol_known {
CF_SYM_KNOWN {
cf_assert_symbol($1, SYM_FILTER);
$$ = $1->filter;
}
| { cf_push_scope(NULL); } filter_body {
| {
cf_enter_filters();
cf_push_scope(new_config, NULL);
this_function = NULL;
} filter_body {
struct filter *f = cfg_alloc(sizeof(struct filter));
*f = (struct filter) { .root = $2 };
$$ = f;
cf_pop_scope();
cf_pop_scope(new_config);
cf_exit_filters();
}
;
where_filter:
WHERE term {
WHERE {
cf_enter_filters();
} term {
/* Construct 'IF term THEN { ACCEPT; } ELSE { REJECT; }' */
$$ = f_new_where($2);
$$ = f_new_where($3);
cf_exit_filters();
}
;
@ -514,15 +584,19 @@ function_body:
;
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 {
this_function = cf_define_symbol(new_config, $2, SYM_FUNCTION, function, NULL);
cf_enter_filters();
cf_push_scope(new_config, this_function);
} function_args function_type {
/* Make dummy f_line for storing function prototype */
struct f_line *dummy = cfg_allocz(sizeof(struct f_line));
$2->function = dummy;
this_function->function = dummy;
dummy->return_type = $5;
/* Revert the args */
while ($4) {
@ -534,10 +608,12 @@ function_def:
dummy->args++;
}
} function_body {
$6->args = $2->function->args;
$6->arg_list = $2->function->arg_list;
$2->function = $6;
cf_pop_scope();
$7->args = this_function->function->args;
$7->arg_list = this_function->function->arg_list;
$7->return_type = this_function->function->return_type;
$2->function = $7;
cf_pop_scope(new_config);
cf_exit_filters();
}
;
@ -547,7 +623,7 @@ cmds: /* EMPTY */ { $$ = NULL; }
| cmds_int { $$ = $1.begin; }
;
cmds_scoped: { cf_push_soft_scope(); } cmds { cf_pop_soft_scope(); $$ = $2; } ;
cmds_scoped: { cf_push_soft_scope(new_config); } cmds { cf_pop_soft_scope(new_config); $$ = $2; } ;
cmd_var: var | cmd ;
@ -596,10 +672,10 @@ set_atom:
| 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, 1), &($$)) > F_RETURN) cf_error("Runtime error");
$$ = cf_eval_tmp($2, T_VOID);
if (!f_valid_set_type($$.type)) cf_error("Set-incompatible type");
}
| symbol_known {
| 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;
@ -608,13 +684,13 @@ set_atom:
switch_atom:
NUM { $$.type = T_INT; $$.val.i = $1; }
| '(' term ')' { $$.type = T_INT; $$.val.i = f_eval_int(f_linearize($2, 1)); }
| '(' term ')' { $$ = cf_eval_tmp($2, T_INT); }
| fipa { $$ = $1; }
| ENUM { $$.type = pair_a($1); $$.val.i = pair_b($1); }
;
cnum:
term { $$ = f_eval_int(f_linearize($1, 1)); }
term { $$ = cf_eval_int($1); }
pair_item:
'(' cnum ',' cnum ')' { $$ = f_new_pair_item($2, $2, $4, $4); }
@ -715,7 +791,7 @@ switch_body: /* EMPTY */ { $$ = NULL; }
;
bgp_path_expr:
symbol_value { $$ = $1; }
lvalue { $$ = f_lval_getter(&$1); }
| '(' term ')' { $$ = $2; }
;
@ -739,13 +815,14 @@ bgp_path_tail:
;
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, }); }
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, }); }
| BYTETEXT { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BYTESTRING, .val.bs = $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, }); }
| '[' ']' { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_SET, .val.t = NULL, }); }
| '[' set_items ']' {
DBG( "We've got a set here..." );
@ -764,48 +841,38 @@ constructor:
;
/* This generates the function_call variable list backwards. */
var_list: /* EMPTY */ { $$ = NULL; }
/* This generates the function_call variable list backwards */
var_list_r:
/* EMPTY */ { $$ = NULL; }
| term { $$ = $1; }
| var_list ',' term { $$ = $3; $$->next = $1; }
| var_list_r ',' term { $$ = $3; $$->next = $1; }
;
var_list: var_list_r
{
$$ = NULL;
/* Revert the var_list_r */
while ($1) {
struct f_inst *tmp = $1;
$1 = $1->next;
tmp->next = $$;
$$ = tmp;
}
}
;
function_call:
symbol_known '(' var_list ')'
CF_SYM_KNOWN '(' var_list ')'
{
if ($1->class != SYM_FUNCTION)
cf_error("You can't call something which is not a function. Really.");
/* Revert the var_list */
struct f_inst *args = NULL;
while ($3) {
struct f_inst *tmp = $3;
$3 = $3->next;
tmp->next = args;
args = tmp;
}
$$ = f_new_inst(FI_CALL, args, $1);
$$ = f_new_inst(FI_CALL, $3, $1);
}
;
symbol_value: symbol_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:
GW { $$ = f_new_static_attr(T_IP, SA_GW, 0); }
@ -818,6 +885,30 @@ static_attr:
| GW_MPLS { $$ = f_new_static_attr(T_INT, SA_GW_MPLS, 0); }
;
term_dot_method: term '.' { f_method_call_start($1); } method_name_cont { f_method_call_end(); $$ = $4; };
method_name_cont:
CF_SYM_METHOD_BARE {
$$ = f_dispatch_method($1, FM.object, NULL, 1);
}
| CF_SYM_METHOD_ARGS {
f_method_call_args();
} '(' var_list ')' {
$$ = f_dispatch_method($1, FM.object, $4, 1);
}
| static_attr {
if (FM.object->type != T_ROUTE)
cf_error("Getting a route attribute from %s, need a route", f_type_name(FM.object->type));
$$ = f_new_inst(FI_RTA_GET, FM.object, $1);
}
| CF_SYM_KNOWN {
if ($1->class != SYM_ATTRIBUTE)
cf_error("Not a method of %s: %s", f_type_name(FM.object->type), $1->name);
if (FM.object->type != T_ROUTE)
cf_error("Getting a route attribute from %s, need a route", f_type_name(FM.object->type));
$$ = f_new_inst(FI_EA_GET, FM.object, $1->attribute);
}
;
term:
'(' term ')' { $$ = $2; }
| term '+' term { $$ = f_new_inst(FI_ADD, $1, $3); }
@ -839,85 +930,67 @@ term:
| '!' term { $$ = f_new_inst(FI_NOT, $2); }
| DEFINED '(' term ')' { $$ = f_new_inst(FI_DEFINED, $3); }
| symbol_value { $$ = $1; }
| constant { $$ = $1; }
| constructor { $$ = $1; }
| static_attr { $$ = f_new_inst(FI_RTA_GET, $1); }
| lvalue { $$ = f_lval_getter(&$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_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); }
| term '.' DATA { $$ = f_new_inst(FI_PAIR_DATA, $1); }
| term '.' DATA1 { $$ = f_new_inst(FI_LC_DATA1, $1); }
| term '.' DATA2 { $$ = f_new_inst(FI_LC_DATA2, $1); }
| term '.' MIN { $$ = f_new_inst(FI_MIN, $1); }
| term '.' MAX { $$ = f_new_inst(FI_MAX, $1); }
| term_dot_method
/* 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_get_empty(T_PATH)); }
| '-' EMPTY '-' { $$ = f_new_inst(FI_CONSTANT, f_get_empty(T_CLIST)); }
| '-' '-' EMPTY '-' '-' { $$ = f_new_inst(FI_CONSTANT, f_get_empty(T_ECLIST)); }
| '-' '-' '-' EMPTY '-' '-' '-' { $$ = f_new_inst(FI_CONSTANT, f_get_empty(T_LCLIST)); }
| '+' 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); }
| PREPEND '(' term ',' term ')' {
$$ = f_dispatch_method_x("prepend", $3->type, $3, $5);
cf_warn("prepend(x,y) function is deprecated, please use x.prepend(y)");
}
| ADD '(' term ',' term ')' {
$$ = f_dispatch_method_x("add", $3->type, $3, $5);
cf_warn("add(x,y) function is deprecated, please use x.add(y)");
}
| DELETE '(' term ',' term ')' {
$$ = f_dispatch_method_x("delete", $3->type, $3, $5);
cf_warn("delete(x,y) function is deprecated, please use x.delete(y)");
}
| FILTER '(' term ',' term ')' {
$$ = f_dispatch_method_x("filter", $3->type, $3, $5);
cf_warn("filter(x,y) function is deprecated, please use x.filter(y)");
}
| ROA_CHECK '(' rtable ')' { $$ = f_implicit_roa_check($3); }
| ROA_CHECK '(' rtable ',' term ',' term ')' { $$ = f_new_inst(FI_ROA_CHECK, $5, $7, $3); }
| FORMAT '(' term ')' { $$ = f_new_inst(FI_FORMAT, $3); }
| term_bs
| function_call
;
term_bs:
FROM_HEX '(' term ')' { $$ = f_new_inst(FI_FROM_HEX, $3); }
;
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;
}
;
var_init:
/* empty */ { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { }); }
| '=' term { $$ = $2; }
;
var:
type symbol var_init ';' {
struct symbol *sym = cf_define_symbol($2, SYM_VARIABLE | $1, offset, f_new_var(sym_->scope));
$$ = f_new_inst(FI_VAR_INIT, $3, sym);
type symbol '=' term ';' {
struct symbol *sym = cf_define_symbol(new_config, $2, SYM_VARIABLE | $1, offset, f_new_var(sym_->scope));
$$ = f_new_inst(FI_VAR_INIT, $4, sym);
}
| type symbol ';' {
struct symbol *sym = cf_define_symbol(new_config, $2, SYM_VARIABLE | $1, offset, f_new_var(sym_->scope));
$$ = f_new_inst(FI_VAR_INIT0, sym);
}
;
for_var:
type symbol { $$ = cf_define_symbol($2, SYM_VARIABLE | $1, offset, f_new_var(sym_->scope)); }
type symbol { $$ = cf_define_symbol(new_config, $2, SYM_VARIABLE | $1, offset, f_new_var(sym_->scope)); }
| CF_SYM_KNOWN { $$ = $1; cf_assert_symbol($1, SYM_VARIABLE); }
;
@ -933,74 +1006,68 @@ cmd:
}
| FOR {
/* Reserve space for walk data on stack */
cf_push_block_scope();
conf_this_scope->slots += 2;
cf_push_block_scope(new_config);
new_config->current_scope->slots += 2;
} for_var IN
/* Parse term in the parent scope */
{ conf_this_scope->active = 0; } term { conf_this_scope->active = 1; }
DO cmd {
cf_pop_block_scope();
$$ = f_new_inst(FI_FOR_INIT, $6, $3);
$$->next = f_new_inst(FI_FOR_NEXT, $3, $9);
{
ASSERT_DIE(f_for_stored_scope == NULL);
f_for_stored_scope = new_config->current_scope;
new_config->current_scope = new_config->current_scope->next;
} term {
ASSERT_DIE(f_for_stored_scope);
new_config->current_scope = f_for_stored_scope;
f_for_stored_scope = NULL;
}
| symbol_known '=' term ';' {
switch ($1->class) {
case SYM_VARIABLE_RANGE:
$$ = f_new_inst(FI_VAR_SET, $3, $1);
break;
case SYM_ATTRIBUTE:
if ($1->attribute->readonly)
cf_error("Attribute %s is read-only", $1->attribute->name);
$$ = f_new_inst(FI_EA_SET, $3, $1->attribute);
break;
default:
cf_error("Can't assign to symbol %s", $1->name);
}
DO cmd {
cf_pop_block_scope(new_config);
$$ = f_for_cycle($3, $6, $9);
}
| lvalue '=' term ';' {
$$ = f_lval_setter(&$1, $3);
}
| RETURN term ';' {
DBG( "Ook, we'll return the value\n" );
if (!this_function)
cf_error("Can't return from a non-function, use accept or reject instead.");
if (this_function->function->return_type == T_VOID)
{
if ($2->type != T_VOID)
cf_warn("Inferring function %s return type from its return value: %s", this_function->name, f_type_name($2->type));
((struct f_line *) this_function->function)->return_type = $2->type;
}
else if (this_function->function->return_type != $2->type)
cf_error("Can't return type %s from function %s, expected %s",
f_type_name($2->type), this_function->name, f_type_name(this_function->function->return_type));
$$ = f_new_inst(FI_RETURN, $2);
}
| static_attr '=' term ';' {
if ($1.readonly)
cf_error( "This static attribute is read-only.");
$$ = f_new_inst(FI_RTA_SET, $3, $1);
}
| UNSET '(' symbol_known ')' ';' {
| UNSET '(' CF_SYM_KNOWN ')' ';' {
if ($3->class != SYM_ATTRIBUTE)
cf_error("Can't unset %s", $3->name);
if ($3->attribute->readonly)
cf_error("Attribute %s is read-only", $3->attribute->name);
$$ = f_new_inst(FI_EA_UNSET, $3->attribute);
}
| 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;
| break_command var_list_r ';' {
$$ = f_print($2, !!$2, $1);
}
| PRINT print_list ';' {
$$ = f_new_inst(FI_PRINT, $2);
$$->next = f_new_inst(FI_FLUSH);
| PRINT var_list_r ';' {
$$ = f_print($2, 1, F_NOP);
}
| PRINTN print_list ';' {
$$ = f_new_inst(FI_PRINT, $2);
| PRINTN var_list_r ';' {
$$ = f_print($2, 0, F_NOP);
}
| function_call ';' { $$ = f_new_inst(FI_DROP_RESULT, $1); }
| CASE term '{' switch_body '}' {
$$ = f_new_inst(FI_SWITCH, $2, $4);
}
| symbol_known '.' EMPTY ';' { $$ = f_generate_empty($1); }
| symbol_known '.' PREPEND '(' term ')' ';' { $$ = f_generate_complex_sym( FI_PATH_PREPEND, $1, $5 ); }
| symbol_known '.' ADD '(' term ')' ';' { $$ = f_generate_complex_sym( FI_CLIST_ADD, $1, $5 ); }
| symbol_known '.' DELETE '(' term ')' ';' { $$ = f_generate_complex_sym( FI_CLIST_DEL, $1, $5 ); }
| symbol_known '.' FILTER '(' term ')' ';' { $$ = f_generate_complex_sym( FI_CLIST_FILTER, $1, $5 ); }
| lvalue '.' {
f_method_call_start(f_lval_getter(&$1));
} method_name_cont ';' {
f_method_call_end();
$$ = f_lval_setter(&$1, $4);
}
| 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); }
;
@ -1011,17 +1078,23 @@ get_cf_position:
};
lvalue:
symbol_known {
switch ($1->class) {
case SYM_VARIABLE_RANGE:
$$ = (struct f_lval) { .type = F_LVAL_VARIABLE, .sym = $1 };
break;
case SYM_ATTRIBUTE:
$$ = (struct f_lval) { .type = F_LVAL_EA, .da = $1->attribute };
break;
}
}
| static_attr { $$ = (struct f_lval) { .type = F_LVAL_SA, .sa = $1 }; }
CF_SYM_KNOWN {
switch ($1->class)
{
case SYM_CONSTANT_RANGE:
$$ = (struct f_lval) { .type = F_LVAL_CONSTANT, .sym = $1, };
break;
case SYM_VARIABLE_RANGE:
$$ = (struct f_lval) { .type = F_LVAL_VARIABLE, .sym = $1, };
break;
case SYM_ATTRIBUTE:
$$ = (struct f_lval) { .type = F_LVAL_EA, .da = $1->attribute, .rte = f_new_inst(FI_CURRENT_ROUTE), };
break;
default:
cf_error("Variable name or attribute name required");
}
}
| static_attr { $$ = (struct f_lval) { .type = F_LVAL_SA, .sa = $1, .rte = f_new_inst(FI_CURRENT_ROUTE), }; }
;
CF_END

View File

@ -16,7 +16,8 @@
#include "lib/unaligned.h"
#include "lib/net.h"
#include "lib/ip.h"
#include "nest/rt.h"
#include "lib/hash.h"
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
#include "lib/attrs.h"
@ -27,6 +28,8 @@
static const char * const f_type_str[] = {
[T_VOID] = "void",
[T_NONE] = "none",
[T_OPAQUE] = "opaque byte string",
[T_IFACE] = "interface",
@ -47,6 +50,7 @@ static const char * const f_type_str[] = {
[T_IP] = "ip",
[T_NET] = "prefix",
[T_STRING] = "string",
[T_BYTESTRING] = "bytestring",
[T_PATH_MASK] = "bgpmask",
[T_PATH_MASK_ITEM] = "bgpmask item",
[T_PATH] = "bgppath",
@ -57,14 +61,19 @@ static const char * const f_type_str[] = {
[T_LCLIST] = "lclist",
[T_RD] = "rd",
[T_ROUTE] = "route",
[T_ROUTES_BLOCK] = "block of routes",
[T_SET] = "set",
[T_PREFIX_SET] = "prefix set",
};
STATIC_ASSERT((1 << (8 * sizeof(btype))) == ARRAY_SIZE(f_type_str));
const char *
f_type_name(btype t)
{
return (t < ARRAY_SIZE(f_type_str)) ? (f_type_str[t] ?: "?") : "?";
return f_type_str[t] ?: "?";
}
btype
@ -75,25 +84,13 @@ f_type_element_type(btype t)
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_path = {
.type = T_PATH,
.val.ad = &null_adata,
}, f_const_empty_clist = {
.type = T_CLIST,
.val.ad = &null_adata,
}, f_const_empty_eclist = {
.type = T_ECLIST,
.val.ad = &null_adata,
}, f_const_empty_lclist = {
.type = T_LCLIST,
.val.ad = &null_adata,
}, f_const_empty_prefix_set = {
const struct f_val f_const_empty_prefix_set = {
.type = T_PREFIX_SET,
.val.ti = &f_const_empty_trie,
};
@ -208,11 +205,21 @@ val_compare(const struct f_val *v1, const struct f_val *v2)
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:
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)
{
@ -277,6 +284,8 @@ val_same(const struct f_val *v1, const struct f_val *v2)
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:
@ -290,6 +299,15 @@ val_same(const struct f_val *v1, const struct f_val *v2)
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 rte_same(v1->val.rte, v2->val.rte);
case T_ROUTES_BLOCK:
if (v1->val.rte_block.len != v2->val.rte_block.len)
return 0;
for (uint i=0; i < v1->val.rte_block.len; i++)
if (!rte_same(v1->val.rte_block.rte[i], v2->val.rte_block.rte[i]))
return 0;
return 1;
default:
bug("Invalid type in val_same(): %x", v1->type);
}
@ -563,6 +581,106 @@ val_in_range(const struct f_val *v1, const struct f_val *v2)
return F_CMP_ERROR;
}
uint
val_hash(struct f_val *v)
{
u64 haux;
mem_hash_init(&haux);
mem_hash_mix_f_val(&haux, v);
return mem_hash_value(&haux);
}
void
mem_hash_mix_f_val(u64 *h, struct f_val *v)
{
mem_hash_mix_num(h, v->type);
#define MX(k) mem_hash_mix(h, &IT(k), sizeof IT(k));
#define IT(k) v->val.k
switch (v->type)
{
case T_VOID:
break;
case T_INT:
case T_BOOL:
case T_PAIR:
case T_QUAD:
case T_ENUM:
MX(i);
break;
case T_EC:
case T_RD:
MX(ec);
break;
case T_LC:
MX(lc);
break;
case T_IP:
MX(ip);
break;
case T_NET:
mem_hash_mix_num(h, net_hash(IT(net)));
break;
case T_STRING:
mem_hash_mix_str(h, IT(s));
break;
case T_PATH_MASK:
mem_hash_mix(h, IT(path_mask), sizeof(*IT(path_mask)) + IT(path_mask)->len * sizeof (IT(path_mask)->item));
break;
case T_PATH:
case T_CLIST:
case T_ECLIST:
case T_LCLIST:
case T_BYTESTRING:
mem_hash_mix(h, IT(ad)->data, IT(ad)->length);
break;
case T_SET:
MX(t);
break;
case T_PREFIX_SET:
MX(ti);
break;
case T_NONE:
case T_PATH_MASK_ITEM:
case T_ROUTE:
case T_ROUTES_BLOCK:
case T_OPAQUE:
case T_NEXTHOP_LIST:
case T_HOSTENTRY:
case T_IFACE:
bug("Invalid type %s in f_val hashing", f_type_name(v->type));
}
}
/*
* 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 via %s",
rte->src->global_id, rte->net,
rte->sender->req->name,
rte->src->owner->name);
else
buffer_puts(buf, "[No route]");
}
static void
rte_block_format(const struct rte_block *block, buffer *buf)
{
buffer_print(buf, "Block of routes:");
for (uint i = 0; i < block->len; i++)
{
buffer_print(buf, "%s%d: ", i ? "; " : " ", i);
rte_format(block->rte[i], buf);
}
}
/*
* val_format - format filter value
*/
@ -576,6 +694,7 @@ val_format(const struct f_val *v, buffer *buf)
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;
@ -591,6 +710,8 @@ val_format(const struct f_val *v, buffer *buf)
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_block, buf); return;
default: buffer_print(buf, "[unknown type %x]", v->type); return;
}
}
@ -616,7 +737,6 @@ val_dump(const struct f_val *v) {
return val_dump_buffer;
}
struct f_val *
lp_val_copy(struct linpool *lp, const struct f_val *v)
{

View File

@ -12,6 +12,15 @@
#include "nest/bird.h"
#include "lib/type.h"
#include "nest/iface.h"
struct f_method {
struct symbol *sym;
struct f_inst *(*new_inst)(struct f_inst *obj, struct f_inst *args);
const struct f_method *next;
uint arg_num;
enum btype args_type[];
};
/* Filter value; size of this affects filter memory consumption */
struct f_val {
@ -39,20 +48,31 @@ struct f_static_attr {
int readonly:1; /* Don't allow writing */
};
struct f_attr_bit {
const struct ea_class *class;
uint bit;
};
#define f_new_dynamic_attr_bit(_bit, _name) ((struct f_attr_bit) { .bit = _bit, .class = ea_class_find(_name) })
/* Filter l-value type */
enum f_lval_type {
F_LVAL_CONSTANT,
F_LVAL_VARIABLE,
F_LVAL_SA,
F_LVAL_EA,
F_LVAL_ATTR_BIT,
};
/* Filter l-value */
struct f_lval {
enum f_lval_type type;
struct f_inst *rte;
union {
struct symbol *sym;
const struct ea_class *da;
struct f_static_attr sa;
struct f_attr_bit fab;
};
};
@ -201,8 +221,8 @@ trie_match_next_longest_ip6(net_addr_ip6 *n, ip6_addr *found)
#define F_CMP_ERROR 999
const char *f_type_name(btype t);
enum btype f_type_element_type(btype t);
struct sym_scope *f_type_method_scope(btype t);
int val_same(const struct f_val *v1, const struct f_val *v2);
int val_compare(const struct f_val *v1, const struct f_val *v2);
@ -210,6 +230,9 @@ void val_format(const struct f_val *v, buffer *buf);
char *val_format_str(struct linpool *lp, const struct f_val *v);
const char *val_dump(const struct f_val *v);
uint val_hash(struct f_val *);
void mem_hash_mix_f_val(u64 *, struct f_val *);
struct f_val *lp_val_copy(struct linpool *lp, const struct f_val *v);
static inline int val_is_ip4(const struct f_val *v)
@ -234,23 +257,29 @@ const struct adata *lclist_filter(struct linpool *pool, const struct adata *list
/* Special undef value for paths and clists */
static inline int
undef_value(struct f_val v)
val_is_undefined(struct f_val v)
{
return ((v.type == T_PATH) || (v.type == T_CLIST) ||
(v.type == T_ECLIST) || (v.type == T_LCLIST)) &&
(v.val.ad == &null_adata);
}
extern const struct f_val f_const_empty_path, f_const_empty_clist, f_const_empty_eclist, f_const_empty_lclist, f_const_empty_prefix_set;
static inline const struct f_val *f_get_empty(btype t)
extern const struct f_val f_const_empty_prefix_set;
static inline struct f_val f_get_empty(btype t)
{
switch (t) {
case T_PATH: return &f_const_empty_path;
case T_CLIST: return &f_const_empty_clist;
case T_ECLIST: return &f_const_empty_eclist;
case T_LCLIST: return &f_const_empty_lclist;
default: return NULL;
case T_PATH:
case T_CLIST:
case T_ECLIST:
case T_LCLIST:
return (struct f_val) {
.type = t,
.val.ad = &null_adata,
};
default:
return (struct f_val) { .type = T_VOID };
}
}

View File

@ -34,6 +34,9 @@ m4_divert(-1)m4_dnl
# 102 constructor arguments
# 110 constructor attributes
# 103 constructor body
# 111 method constructor body
# 112 instruction constructor call from method constructor
# 113 method constructor symbol registrator
# 104 dump line item content
# (there may be nothing in dump-line content and
# it must be handled specially in phase 2)
@ -48,6 +51,9 @@ m4_define(FID_STRUCT_IN, `m4_divert(101)')
m4_define(FID_NEW_ARGS, `m4_divert(102)')
m4_define(FID_NEW_ATTRIBUTES, `m4_divert(110)')
m4_define(FID_NEW_BODY, `m4_divert(103)')
m4_define(FID_NEW_METHOD, `m4_divert(111)')
m4_define(FID_METHOD_CALL, `m4_divert(112)')
m4_define(FID_TYPE_SIGNATURE, `m4_divert(113)')
m4_define(FID_DUMP_BODY, `m4_divert(104)m4_define([[FID_DUMP_BODY_EXISTS]])')
m4_define(FID_LINEARIZE_BODY, `m4_divert(105)')
m4_define(FID_SAME_BODY, `m4_divert(106)')
@ -120,7 +126,13 @@ FID_IFCONST([[
constargs = 0;
]])
} while (child$1 = child$1->next);
FID_LINEARIZE_BODY
m4_define([[INST_METHOD_NUM_ARGS]],$1)m4_dnl
m4_ifelse($1,1,,[[FID_NEW_METHOD()m4_dnl
struct f_inst *arg$1 = args;
if (args == NULL) cf_error("Not enough arguments"); /* INST_NAME */
args = args->next;
FID_METHOD_CALL() , arg$1]])
FID_LINEARIZE_BODY()m4_dnl
pos = linearize(dest, whati->f$1, pos);
FID_INTERPRET_BODY()')
@ -170,28 +182,31 @@ FID_HIC(,[[
m4_define(ARG, `ARG_ANY($1) ARG_TYPE($1,$2)')
m4_define(ARG_TYPE, `ARG_TYPE_STATIC($1,$2) ARG_TYPE_DYNAMIC($1,$2)')
m4_define(ARG_TYPE_STATIC, `
m4_define(ARG_TYPE_STATIC, `m4_dnl
m4_ifelse($1,1,[[m4_define([[INST_METHOD_OBJECT_TYPE]],$2)]],)m4_dnl
FID_TYPE_SIGNATURE()m4_dnl
method->args_type[m4_eval($1-1)] = $2;
FID_NEW_BODY()m4_dnl
if (f$1->type && (f$1->type != ($2)) && !f_const_promotion(f$1, ($2)))
cf_error("Argument $1 of %s must be of type %s, got type %s",
f_instruction_name(what->fi_code), f_type_name($2), f_type_name(f$1->type));
FID_INTERPRET_BODY()')
m4_define(ARG_TYPE_DYNAMIC, `
m4_define(ARG_TYPE_DYNAMIC, `m4_dnl
FID_INTERPRET_EXEC()m4_dnl
if (v$1.type != ($2))
runtime("Argument $1 of %s must be of type %s, got type %s",
f_instruction_name(what->fi_code), f_type_name($2), f_type_name(v$1.type));
FID_INTERPRET_BODY()')
m4_define(ARG_SAME_TYPE, `
m4_define(ARG_SAME_TYPE, `m4_dnl
FID_NEW_BODY()m4_dnl
if (f$1->type && f$2->type && (f$1->type != f$2->type) &&
!f_const_promotion(f$2, f$1->type) && !f_const_promotion(f$1, f$2->type))
cf_error("Arguments $1 and $2 of %s must be of the same type", f_instruction_name(what->fi_code));
FID_INTERPRET_BODY()')
m4_define(ARG_PREFER_SAME_TYPE, `
m4_define(ARG_PREFER_SAME_TYPE, `m4_dnl
FID_NEW_BODY()m4_dnl
if (f$1->type && f$2->type && (f$1->type != f$2->type))
(void) (f_const_promotion(f$2, f$1->type) || f_const_promotion(f$1, f$2->type));
@ -200,7 +215,7 @@ FID_INTERPRET_BODY()')
# Executing another filter line. This replaces the recursion
# that was needed in the former implementation.
m4_define(LINEX, `FID_INTERPRET_EXEC()LINEX_($1)FID_INTERPRET_NEW()return $1 FID_INTERPRET_BODY()')
m4_define(LINEX_, `do {
m4_define(LINEX_, `do if ($1) {
if (fstk->ecnt + 1 >= fstk->elen) runtime("Filter execution stack overflow");
fstk->estk[fstk->ecnt].pos = 0;
fstk->estk[fstk->ecnt].line = $1;
@ -219,6 +234,12 @@ FID_NEW_ARGS()m4_dnl
, struct f_inst * f$1
FID_NEW_BODY()m4_dnl
whati->f$1 = f$1;
m4_define([[INST_METHOD_NUM_ARGS]],$1)m4_dnl
FID_NEW_METHOD()m4_dnl
struct f_inst *arg$1 = args;
if (args == NULL) cf_error("Not enough arguments"); /* INST_NAME */
args = NULL; /* The rest is the line itself */
FID_METHOD_CALL() , arg$1
FID_DUMP_BODY()m4_dnl
f_dump_line(item->fl$1, indent + 1);
FID_LINEARIZE_BODY()m4_dnl
@ -228,9 +249,7 @@ if (!f_same(f1->fl$1, f2->fl$1)) return 0;
FID_ITERATE_BODY()m4_dnl
if (whati->fl$1) BUFFER_PUSH(fit->lines) = whati->fl$1;
FID_INTERPRET_EXEC()m4_dnl
do { if (whati->fl$1) {
LINEX_(whati->fl$1);
} } while(0)
LINEX_(whati->fl$1)
FID_INTERPRET_NEW()m4_dnl
return whati->f$1
FID_INTERPRET_BODY()')
@ -269,6 +288,29 @@ m4_define(STATIC_ATTR, `FID_MEMBER(struct f_static_attr, sa, f1->sa.sa_code != f
m4_define(DYNAMIC_ATTR, `FID_MEMBER(const struct ea_class *, da, f1->da != f2->da,,)')
m4_define(ACCESS_RTE, `FID_HIC(,[[do { if (!fs->rte) runtime("No route to access"); } while (0)]],NEVER_CONSTANT())')
# Method constructor block
m4_define(METHOD_CONSTRUCTOR, `m4_dnl
FID_NEW_METHOD()m4_dnl
if (args) cf_error("Too many arguments");
m4_define([[INST_IS_METHOD]])
m4_define([[INST_METHOD_NAME]],$1)
FID_INTERPRET_BODY()')
# Short method constructor
# $1 = type
# $2 = name
# $3 = method inputs
# method outputs are always 1
# $4 = code
m4_define(METHOD, `m4_dnl
INST([[FI_METHOD__]]$1[[__]]$2, m4_eval($3 + 1), 1) {
ARG(1, $1);
$4
METHOD_CONSTRUCTOR("$2");
}')
m4_define(METHOD_R, `METHOD($1, $2, 0, [[ RESULT($3, $4, $5) ]])')
# 2) Code wrapping
# The code produced in 1xx temporary diversions is a raw code without
# any auxiliary commands and syntactical structures around. When the
@ -288,6 +330,7 @@ m4_define(ACCESS_RTE, `FID_HIC(,[[do { if (!fs->rte) runtime("No route to access
# 10 iterate
# 1 union in struct f_inst
# 3 constructors + interpreter
# 11 method constructors
#
# These global diversions contain blocks of code that can be directly
# put into the final file, yet it still can't be written out now as
@ -307,6 +350,9 @@ m4_define(FID_DUMP_CALLER, `FID_ZONE(7, Dump line caller)')
m4_define(FID_LINEARIZE, `FID_ZONE(8, Linearize)')
m4_define(FID_SAME, `FID_ZONE(9, Comparison)')
m4_define(FID_ITERATE, `FID_ZONE(10, Iteration)')
m4_define(FID_METHOD, `FID_ZONE(11, Method constructor)')
m4_define(FID_METHOD_SCOPE_INIT, `FID_ZONE(12, Method scope initializator)')
m4_define(FID_METHOD_REGISTER, `FID_ZONE(13, Method registrator)')
# This macro does all the code wrapping. See inline comments.
m4_define(INST_FLUSH, `m4_ifdef([[INST_NAME]], [[
@ -334,7 +380,8 @@ m4_undivert(102)m4_dnl
[[m4_dnl The one case in The Big Switch inside interpreter
case INST_NAME():
#define whati (&(what->i_]]INST_NAME()[[))
m4_ifelse(m4_eval(INST_INVAL() > 0), 1, [[if (fstk->vcnt < INST_INVAL()) runtime("Stack underflow"); fstk->vcnt -= INST_INVAL(); ]])
m4_ifelse(m4_eval(INST_INVAL() > 0), 1, [[if (fstk->vcnt < INST_INVAL()) runtime("Stack underflow");
fstk->vcnt -= INST_INVAL();]])
m4_undivert(108)m4_dnl
#undef whati
break;
@ -364,6 +411,29 @@ m4_undivert(102)m4_dnl
}
]])
m4_ifdef([[INST_IS_METHOD]],m4_dnl
FID_METHOD()m4_dnl
[[struct f_inst * NONNULL(1)
f_new_method_]]INST_NAME()[[(struct f_inst *obj, struct f_inst *args)
{
/* Unwind the arguments (INST_METHOD_NUM_ARGS) */
m4_undivert(111)m4_dnl
return f_new_inst(INST_NAME, obj
m4_undivert(112)
);
}
FID_METHOD_SCOPE_INIT()m4_dnl
[INST_METHOD_OBJECT_TYPE] = {},
FID_METHOD_REGISTER()m4_dnl
method = lp_allocz(global_root_scope_linpool, sizeof(struct f_method) + INST_METHOD_NUM_ARGS * sizeof(enum btype));
method->new_inst = f_new_method_]]INST_NAME()[[;
method->arg_num = INST_METHOD_NUM_ARGS;
m4_undivert(113)
f_register_method(INST_METHOD_OBJECT_TYPE, INST_METHOD_NAME, method);
]])m4_dnl
FID_DUMP_CALLER()m4_dnl Case in another big switch used in instruction dumping (debug)
case INST_NAME(): f_dump_line_item_]]INST_NAME()[[(item, indent + 1); break;
@ -417,6 +487,8 @@ m4_define([[INST_INVAL]], [[$2]])m4_dnl instruction input value count,
m4_define([[INST_OUTVAL]], [[$3]])m4_dnl instruction output value count,
m4_undefine([[INST_NEVER_CONSTANT]])m4_dnl reset NEVER_CONSTANT trigger,
m4_undefine([[INST_RESULT_TYPE]])m4_dnl and reset RESULT_TYPE value.
m4_undefine([[INST_IS_METHOD]])m4_dnl and reset method constructor request.
m4_undefine([[INST_METHOD_OBJECT_TYPE]],)m4_dnl reset method object type,
FID_INTERPRET_BODY()m4_dnl By default, every code is interpreter code.
')
@ -502,8 +574,8 @@ fi_constant(struct f_inst *what, struct f_val val)
return what;
}
static int
f_const_promotion(struct f_inst *arg, btype want)
int
f_const_promotion_(struct f_inst *arg, btype want, int update)
{
if (arg->fi_code != FI_CONSTANT)
return 0;
@ -511,15 +583,17 @@ f_const_promotion(struct f_inst *arg, btype want)
struct f_val *c = &arg->i_FI_CONSTANT.val;
if ((c->type == T_IP) && ipa_is_ip4(c->val.ip) && (want == T_QUAD)) {
*c = (struct f_val) {
.type = T_QUAD,
.val.i = ipa_to_u32(c->val.ip),
};
if (update)
*c = (struct f_val) {
.type = T_QUAD,
.val.i = ipa_to_u32(c->val.ip),
};
return 1;
}
else if ((c->type == T_SET) && (!c->val.t) && (want == T_PREFIX_SET)) {
*c = f_const_empty_prefix_set;
if (update)
*c = f_const_empty_prefix_set;
return 1;
}
@ -540,6 +614,58 @@ FID_WR_PUT(3)
#undef v3
#undef vv
/* Method constructor wrappers */
FID_WR_PUT(11)
#if defined(__GNUC__) && __GNUC__ >= 6
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Woverride-init"
#endif
static struct sym_scope f_type_method_scopes[] = {
FID_WR_PUT(12)
};
#if defined(__GNUC__) && __GNUC__ >= 6
#pragma GCC diagnostic pop
#endif
struct sym_scope *f_type_method_scope(enum btype t)
{
return (t < ARRAY_SIZE(f_type_method_scopes)) ? &f_type_method_scopes[t] : NULL;
}
static void
f_register_method(enum btype t, const byte *name, struct f_method *dsc)
{
struct sym_scope *scope = &f_type_method_scopes[t];
struct symbol *sym = cf_find_symbol_scope(scope, name);
if (!sym)
{
sym = cf_root_symbol(name, scope);
sym->class = SYM_METHOD;
}
dsc->sym = sym;
dsc->next = sym->method;
sym->method = dsc;
}
extern struct sym_scope global_filter_scope;
void f_type_methods_register(void)
{
struct f_method *method;
FID_WR_PUT(13)
for (uint i = 0; i < ARRAY_SIZE(f_type_method_scopes); i++)
f_type_method_scopes[i].readonly = 1;
f_type_method_scopes[T_ROUTE].next = &global_filter_scope;
}
/* Line dumpers */
#define INDENT (((const char *) f_dump_line_indent_str) + sizeof(f_dump_line_indent_str) - (indent) - 1)
static const char f_dump_line_indent_str[] = " ";

View File

@ -71,6 +71,8 @@
* m4_dnl RTC; route table config
* m4_dnl ACCESS_RTE; this instruction needs route
*
* m4_dnl METHOD_CONSTRUCTOR(name); this instruction is in fact a method of the first argument's type; register it with the given name for that type
*
* m4_dnl FID_MEMBER( custom instruction member
* m4_dnl C type, for storage in structs
* m4_dnl name, how the member is named
@ -98,6 +100,11 @@
*
* Other code is just copied into the interpreter part.
*
* It's also possible to declare type methods in a short way:
*
* m4_dnl METHOD(type, method name, argument count, code)
* m4_dnl METHOD_R(type, method name, argument count, result type, union-field, value)
*
* The filter language uses a simple type system, where values have types
* (constants T_*) and also terms (instructions) are statically typed. Our
* static typing is partial (some terms do not declare types of arguments
@ -493,26 +500,13 @@
INST(FI_DEFINED, 1, 1) {
ARG_ANY(1);
RESULT(T_BOOL, i, (v1.type != T_VOID) && !undef_value(v1));
RESULT(T_BOOL, i, (v1.type != T_VOID) && !val_is_undefined(v1));
}
INST(FI_TYPE, 1, 1) {
ARG_ANY(1); /* There may be more types supporting this operation */
switch (v1.type)
{
case T_NET:
RESULT(T_ENUM_NETTYPE, i, v1.val.net->type);
break;
default:
runtime( "Can't determine type of this item" );
}
}
INST(FI_IS_V4, 1, 1) {
ARG(1, T_IP);
RESULT(T_BOOL, i, ipa_is_ip4(v1.val.ip));
}
METHOD_R(T_NET, type, T_ENUM_NETTYPE, i, v1.val.net->type);
METHOD_R(T_IP, is_v4, T_BOOL, i, ipa_is_ip4(v1.val.ip));
/* Add initialized variable */
INST(FI_VAR_INIT, 1, 0) {
NEVER_CONSTANT;
ARG_ANY(1);
@ -525,6 +519,17 @@
fstk->vcnt = pos + 1;
}
/* Add uninitialized variable */
INST(FI_VAR_INIT0, 0, 0) {
NEVER_CONSTANT;
SYMBOL;
/* New variable is always the last on stack */
uint pos = curline.vbase + sym->offset;
fstk->vstk[pos] = f_get_empty(sym->class & 0xff);
fstk->vcnt = pos + 1;
}
/* Set to indirect value prepared in v1 */
INST(FI_VAR_SET, 1, 0) {
NEVER_CONSTANT;
@ -555,92 +560,81 @@
RESULT_VAL(val);
}
INST(FI_FOR_INIT, 1, 0) {
METHOD_R(T_PATH, empty, T_PATH, ad, &null_adata);
METHOD_R(T_CLIST, empty, T_CLIST, ad, &null_adata);
METHOD_R(T_ECLIST, empty, T_ECLIST, ad, &null_adata);
METHOD_R(T_LCLIST, empty, T_LCLIST, ad, &null_adata);
/* Common loop begin instruction, always created by f_for_cycle() */
INST(FI_FOR_LOOP_START, 0, 3) {
NEVER_CONSTANT;
ARG_ANY(1);
SYMBOL;
FID_NEW_BODY()
ASSERT((sym->class & ~0xff) == SYM_VARIABLE);
/* Repeat the instruction which called us */
ASSERT_DIE(fstk->ecnt > 1);
prevline.pos--;
/* Static type check */
if (f1->type)
{
enum btype t_var = (sym->class & 0xff);
enum btype t_arg = f_type_element_type(f1->type);
if (!t_arg)
cf_error("Value of expression in FOR must be iterable, got %s",
f_type_name(f1->type));
if (t_var != t_arg)
cf_error("Loop variable '%s' in FOR must be %s, is %s",
sym->name, f_type_name(t_arg), f_type_name(t_var));
}
/* There should be exactly three items on the value stack to be taken care of */
fstk->vcnt += 3;
FID_INTERPRET_BODY()
/* And these should also stay there after we finish for the caller instruction */
curline.ventry += 3;
/* Dynamic type check */
if ((sym->class & 0xff) != f_type_element_type(v1.type))
runtime("Mismatched argument and variable type");
/* Assert the iterator variable positioning */
ASSERT_DIE(curline.vbase + sym->offset == fstk->vcnt - 1);
/* Setup the index */
v2 = (struct f_val) { .type = T_INT, .val.i = 0 };
/* Keep v1 and v2 on the stack */
fstk->vcnt += 2;
/* The result type declaration makes no sense here but is needed */
RESULT_TYPE(T_VOID);
}
INST(FI_FOR_NEXT, 2, 0) {
/* Type-specific for_next iterators */
INST(FI_PATH_FOR_NEXT, 3, 0) {
NEVER_CONSTANT;
SYMBOL;
ARG(1, T_PATH);
if (as_path_walk(v1.val.ad, &v2.val.i, &v3.val.i))
LINE(2,0);
/* Type checks are done in FI_FOR_INIT */
METHOD_CONSTRUCTOR("!for_next");
}
/* Loop variable */
struct f_val *var = &fstk->vstk[curline.vbase + sym->offset];
int step = 0;
INST(FI_CLIST_FOR_NEXT, 3, 0) {
NEVER_CONSTANT;
ARG(1, T_CLIST);
if (int_set_walk(v1.val.ad, &v2.val.i, &v3.val.i))
LINE(2,0);
switch(v1.type)
METHOD_CONSTRUCTOR("!for_next");
}
INST(FI_ECLIST_FOR_NEXT, 3, 0) {
NEVER_CONSTANT;
ARG(1, T_ECLIST);
if (ec_set_walk(v1.val.ad, &v2.val.i, &v3.val.ec))
LINE(2,0);
METHOD_CONSTRUCTOR("!for_next");
}
INST(FI_LCLIST_FOR_NEXT, 3, 0) {
NEVER_CONSTANT;
ARG(1, T_LCLIST);
if (lc_set_walk(v1.val.ad, &v2.val.i, &v3.val.lc))
LINE(2,0);
METHOD_CONSTRUCTOR("!for_next");
}
INST(FI_ROUTES_BLOCK_FOR_NEXT, 3, 0) {
NEVER_CONSTANT;
ARG(1, T_ROUTES_BLOCK);
if (v2.val.i < v1.val.rte_block.len)
{
case T_PATH:
var->type = T_INT;
step = as_path_walk(v1.val.ad, &v2.val.i, &var->val.i);
break;
case T_CLIST:
var->type = T_PAIR;
step = int_set_walk(v1.val.ad, &v2.val.i, &var->val.i);
break;
case T_ECLIST:
var->type = T_EC;
step = ec_set_walk(v1.val.ad, &v2.val.i, &var->val.ec);
break;
case T_LCLIST:
var->type = T_LC;
step = lc_set_walk(v1.val.ad, &v2.val.i, &var->val.lc);
break;
default:
runtime( "Clist or lclist expected" );
v3.val.rte = v1.val.rte_block.rte[v2.val.i++];
LINE(2,0);
}
if (step)
{
/* Keep v1 and v2 on the stack */
fstk->vcnt += 2;
/* Repeat this instruction */
curline.pos--;
/* Execute the loop body */
LINE(1, 0);
/* Space for loop variable, may be unused */
fstk->vcnt += 1;
}
else
var->type = T_VOID;
METHOD_CONSTRUCTOR("!for_next");
}
INST(FI_CONDITION, 1, 0) {
@ -651,17 +645,16 @@
LINE(3,0);
}
INST(FI_PRINT, 0, 0) {
INST(FI_PRINT, 1, 0) {
NEVER_CONSTANT;
VARARG;
ARG_ANY(1);
if (whati->varcount && !(fs->flags & FF_SILENT))
if (!(fs->flags & FF_SILENT))
{
if (!fs->buf.class)
log_prepare(&fs->buf, *L_INFO);
for (uint i=0; i<whati->varcount; i++)
val_format(&(vv(i)), &fs->buf.buf);
val_format(&v1, &fs->buf.buf);
}
}
@ -686,18 +679,27 @@
}
}
INST(FI_RTA_GET, 0, 1) {
INST(FI_CURRENT_ROUTE, 0, 1) {
NEVER_CONSTANT;
ACCESS_RTE;
RESULT_TYPE(T_ROUTE);
RESULT_VAL([[(struct f_val) { .type = T_ROUTE, .val.rte = fs->rte, }]]);
}
INST(FI_RTA_GET, 1, 1) {
{
ARG(1, T_ROUTE);
STATIC_ATTR;
ACCESS_RTE;
struct rte *rte = v1.val.rte;
switch (sa.sa_code)
{
case SA_NET: RESULT(sa.type, net, fs->rte->net); break;
case SA_PROTO: RESULT(sa.type, s, fs->rte->src->owner->name); break;
case SA_NET: RESULT(sa.type, net, rte->net); break;
case SA_PROTO: RESULT(sa.type, s, rte->src->owner->name); break;
default:
{
struct eattr *nhea = ea_find(fs->rte->attrs, &ea_gen_nexthop);
struct eattr *nhea = ea_find(rte->attrs, &ea_gen_nexthop);
struct nexthop_adata *nhad = nhea ? (struct nexthop_adata *) nhea->u.ptr : NULL;
struct nexthop *nh = nhad ? &nhad->nh : NULL;
@ -856,13 +858,13 @@
}
}
INST(FI_EA_GET, 0, 1) { /* Access to extended attributes */
INST(FI_EA_GET, 1, 1) { /* Access to extended attributes */
ARG(1, T_ROUTE);
DYNAMIC_ATTR;
ACCESS_RTE;
RESULT_TYPE(da->type);
{
const struct f_val *empty;
const eattr *e = ea_find(fs->rte->attrs, da->id);
struct f_val empty;
const eattr *e = ea_find(v1.val.rte->attrs, da->id);
if (e)
{
@ -879,8 +881,8 @@
}]]);
}
}
else if (empty = f_get_empty(da->type))
RESULT_VAL(*empty);
else if ((empty = f_get_empty(da->type)).type != T_VOID)
RESULT_VAL(empty);
else
RESULT_VOID;
}
@ -898,8 +900,8 @@
bug("Unsupported attribute type");
switch (da->type) {
case T_OPAQUE:
case T_IFACE:
case T_OPAQUE:
runtime( "Setting opaque attribute is not allowed" );
break;
@ -939,20 +941,16 @@
RESULT_VAL(v1);
}
INST(FI_LENGTH, 1, 1) { /* Get length of */
ARG_ANY(1);
switch(v1.type) {
case T_NET: RESULT(T_INT, i, net_pxlen(v1.val.net)); break;
case T_PATH: RESULT(T_INT, i, as_path_getlen(v1.val.ad)); break;
case T_CLIST: RESULT(T_INT, i, int_set_get_size(v1.val.ad)); break;
case T_ECLIST: RESULT(T_INT, i, ec_set_get_size(v1.val.ad)); break;
case T_LCLIST: RESULT(T_INT, i, lc_set_get_size(v1.val.ad)); break;
default: runtime( "Prefix, path, clist or eclist expected" );
}
}
/* Get length of */
METHOD_R(T_NET, len, T_INT, i, net_pxlen(v1.val.net));
METHOD_R(T_PATH, len, T_INT, i, as_path_getlen(v1.val.ad));
METHOD_R(T_CLIST, len, T_INT, i, int_set_get_size(v1.val.ad));
METHOD_R(T_ECLIST, len, T_INT, i, ec_set_get_size(v1.val.ad));
METHOD_R(T_LCLIST, len, T_INT, i, lc_set_get_size(v1.val.ad));
INST(FI_NET_SRC, 1, 1) { /* Get src prefix */
ARG(1, T_NET);
METHOD_CONSTRUCTOR("src");
net_addr_union *net = (void *) v1.val.net;
net_addr *src = falloc(sizeof(net_addr_ip6));
@ -988,6 +986,7 @@
INST(FI_NET_DST, 1, 1) { /* Get dst prefix */
ARG(1, T_NET);
METHOD_CONSTRUCTOR("dst");
net_addr_union *net = (void *) v1.val.net;
net_addr *dst = falloc(sizeof(net_addr_ip6));
@ -1021,156 +1020,78 @@
RESULT(T_NET, net, dst);
}
INST(FI_ROA_MAXLEN, 1, 1) { /* Get ROA max prefix length */
ARG(1, T_NET);
/* Get ROA max prefix length */
METHOD(T_NET, maxlen, 0, [[
if (!net_is_roa(v1.val.net))
runtime( "ROA expected" );
RESULT(T_INT, i, (v1.val.net->type == NET_ROA4) ?
((net_addr_roa4 *) v1.val.net)->max_pxlen :
((net_addr_roa6 *) v1.val.net)->max_pxlen);
}
]]);
INST(FI_ASN, 1, 1) { /* Get ROA ASN or community ASN part */
ARG_ANY(1);
RESULT_TYPE(T_INT);
switch(v1.type)
{
case T_NET:
/* Get ROA ASN */
METHOD(T_NET, asn, 0, [[
if (!net_is_roa(v1.val.net))
runtime( "ROA expected" );
RESULT_(T_INT, i, (v1.val.net->type == NET_ROA4) ?
RESULT(T_INT, i, (v1.val.net->type == NET_ROA4) ?
((net_addr_roa4 *) v1.val.net)->asn :
((net_addr_roa6 *) v1.val.net)->asn);
break;
]]);
case T_PAIR:
RESULT_(T_INT, i, v1.val.i >> 16);
break;
case T_LC:
RESULT_(T_INT, i, v1.val.lc.asn);
break;
default:
runtime( "Net, pair or lc expected" );
}
}
INST(FI_IP, 1, 1) { /* Convert prefix to ... */
ARG(1, T_NET);
RESULT(T_IP, ip, net_prefix(v1.val.net));
}
/* Convert prefix to IP */
METHOD_R(T_NET, ip, T_IP, ip, net_prefix(v1.val.net));
INST(FI_ROUTE_DISTINGUISHER, 1, 1) {
ARG(1, T_NET);
METHOD_CONSTRUCTOR("rd");
if (!net_is_vpn(v1.val.net))
runtime( "VPN address expected" );
RESULT(T_RD, ec, net_rd(v1.val.net));
}
INST(FI_AS_PATH_FIRST, 1, 1) { /* Get first ASN from AS PATH */
ARG(1, T_PATH);
u32 as = 0;
as_path_get_first(v1.val.ad, &as);
RESULT(T_INT, i, as);
}
/* Get first ASN from AS PATH */
METHOD_R(T_PATH, first, T_INT, i, ({ u32 as = 0; as_path_get_first(v1.val.ad, &as); as; }));
INST(FI_AS_PATH_LAST, 1, 1) { /* Get last ASN from AS PATH */
ARG(1, T_PATH);
u32 as = 0;
as_path_get_last(v1.val.ad, &as);
RESULT(T_INT, i, as);
}
/* Get last ASN from AS PATH */
METHOD_R(T_PATH, last, T_INT, i, ({ u32 as = 0; as_path_get_last(v1.val.ad, &as); as; }));
INST(FI_AS_PATH_LAST_NAG, 1, 1) { /* Get last ASN from non-aggregated part of AS PATH */
ARG(1, T_PATH);
RESULT(T_INT, i, as_path_get_last_nonaggregated(v1.val.ad));
}
/* Get last ASN from non-aggregated part of AS PATH */
METHOD_R(T_PATH, last_nonaggregated, T_INT, i, as_path_get_last_nonaggregated(v1.val.ad));
INST(FI_PAIR_DATA, 1, 1) { /* Get data part from the standard community */
ARG(1, T_PAIR);
RESULT(T_INT, i, v1.val.i & 0xFFFF);
}
/* Get ASN part from the standard community ASN */
METHOD_R(T_PAIR, asn, T_INT, i, v1.val.i >> 16);
INST(FI_LC_DATA1, 1, 1) { /* Get data1 part from the large community */
ARG(1, T_LC);
RESULT(T_INT, i, v1.val.lc.ldp1);
}
/* Get data part from the standard community */
METHOD_R(T_PAIR, data, T_INT, i, v1.val.i & 0xFFFF);
INST(FI_LC_DATA2, 1, 1) { /* Get data2 part from the large community */
ARG(1, T_LC);
RESULT(T_INT, i, v1.val.lc.ldp2);
}
/* Get ASN part from the large community */
METHOD_R(T_LC, asn, T_INT, i, v1.val.lc.asn);
INST(FI_MIN, 1, 1) { /* Get minimum element from list */
ARG_ANY(1);
RESULT_TYPE(f_type_element_type(v1.type));
switch(v1.type)
{
case T_CLIST:
{
u32 val = 0;
int_set_min(v1.val.ad, &val);
RESULT_(T_PAIR, i, val);
}
break;
/* Get data1 part from the large community */
METHOD_R(T_LC, data1, T_INT, i, v1.val.lc.ldp1);
case T_ECLIST:
{
u64 val = 0;
ec_set_min(v1.val.ad, &val);
RESULT_(T_EC, ec, val);
}
break;
/* Get data2 part from the large community */
METHOD_R(T_LC, data2, T_INT, i, v1.val.lc.ldp2);
case T_LCLIST:
{
lcomm val = { 0, 0, 0 };
lc_set_min(v1.val.ad, &val);
RESULT_(T_LC, lc, val);
}
break;
/* Get minimum element from clist */
METHOD_R(T_CLIST, min, T_PAIR, i, ({ u32 val = 0; int_set_min(v1.val.ad, &val); val; }));
default:
runtime( "Clist or lclist expected" );
}
}
/* Get maximum element from clist */
METHOD_R(T_CLIST, max, T_PAIR, i, ({ u32 val = 0; int_set_max(v1.val.ad, &val); val; }));
INST(FI_MAX, 1, 1) { /* Get maximum element from list */
ARG_ANY(1);
RESULT_TYPE(f_type_element_type(v1.type));
switch(v1.type)
{
case T_CLIST:
{
u32 val = 0;
int_set_max(v1.val.ad, &val);
RESULT_(T_PAIR, i, val);
}
break;
/* Get minimum element from eclist */
METHOD_R(T_ECLIST, min, T_EC, ec, ({ u64 val = 0; ec_set_min(v1.val.ad, &val); val; }));
case T_ECLIST:
{
u64 val = 0;
ec_set_max(v1.val.ad, &val);
RESULT_(T_EC, ec, val);
}
break;
/* Get maximum element from eclist */
METHOD_R(T_ECLIST, max, T_EC, ec, ({ u64 val = 0; ec_set_max(v1.val.ad, &val); val; }));
case T_LCLIST:
{
lcomm val = { 0, 0, 0 };
lc_set_max(v1.val.ad, &val);
RESULT_(T_LC, lc, val);
}
break;
/* Get minimum element from lclist */
METHOD_R(T_LCLIST, min, T_LC, lc, ({ lcomm val = {}; lc_set_min(v1.val.ad, &val); val; }));
default:
runtime( "Clist or lclist expected" );
}
}
/* Get maximum element from lclist */
METHOD_R(T_LCLIST, max, T_LC, lc, ({ lcomm val = {}; lc_set_max(v1.val.ad, &val); val; }));
INST(FI_RETURN, 1, 0) {
NEVER_CONSTANT;
@ -1202,9 +1123,7 @@
NEVER_CONSTANT;
VARARG;
SYMBOL;
/* Fake result type declaration */
RESULT_TYPE(T_VOID);
RESULT_TYPE(sym->function->return_type);
FID_NEW_BODY()
ASSERT(sym->class == SYM_FUNCTION);
@ -1302,7 +1221,6 @@
FID_HIC(,break,return NULL);
}
}
/* It is actually possible to have t->data NULL */
LINEX(t->data);
}
@ -1310,6 +1228,7 @@
INST(FI_IP_MASK, 2, 1) { /* IP.MASK(val) */
ARG(1, T_IP);
ARG(2, T_INT);
METHOD_CONSTRUCTOR("mask");
RESULT(T_IP, ip, [[ ipa_is_ip4(v1.val.ip) ?
ipa_from_ip4(ip4_and(ipa_to_ip4(v1.val.ip), ip4_mkmask(v2.val.i))) :
ipa_from_ip6(ip6_and(ipa_to_ip6(v1.val.ip), ip6_mkmask(v2.val.i))) ]]);
@ -1318,165 +1237,252 @@
INST(FI_PATH_PREPEND, 2, 1) { /* Path prepend */
ARG(1, T_PATH);
ARG(2, T_INT);
METHOD_CONSTRUCTOR("prepend");
RESULT(T_PATH, ad, [[ as_path_prepend(fpool, v1.val.ad, v2.val.i) ]]);
}
INST(FI_CLIST_ADD, 2, 1) { /* (Extended) Community list add */
ARG_ANY(1);
ARG_ANY(2);
RESULT_TYPE(f1->type);
if (v1.type == T_PATH)
runtime("Can't add to path");
else if (v1.type == T_CLIST)
{
/* Community (or cluster) list */
struct f_val dummy;
if ((v2.type == T_PAIR) || (v2.type == T_QUAD))
RESULT_(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, v2.val.i) ]]);
/* IP->Quad implicit conversion */
else if (val_is_ip4(&v2))
RESULT_(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
else if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy))
runtime("Can't add set");
else if (v2.type == T_CLIST)
RESULT_(T_CLIST, ad, [[ int_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
else
runtime("Can't add non-pair");
}
else if (v1.type == T_ECLIST)
{
/* v2.val is either EC or EC-set */
if ((v2.type == T_SET) && eclist_set_type(v2.val.t))
runtime("Can't add set");
else if (v2.type == T_ECLIST)
RESULT_(T_ECLIST, ad, [[ ec_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
else if (v2.type != T_EC)
runtime("Can't add non-ec");
else
RESULT_(T_ECLIST, ad, [[ ec_set_add(fpool, v1.val.ad, v2.val.ec) ]]);
}
else if (v1.type == T_LCLIST)
{
/* v2.val is either LC or LC-set */
if ((v2.type == T_SET) && lclist_set_type(v2.val.t))
runtime("Can't add set");
else if (v2.type == T_LCLIST)
RESULT_(T_LCLIST, ad, [[ lc_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
else if (v2.type != T_LC)
runtime("Can't add non-lc");
else
RESULT_(T_LCLIST, ad, [[ lc_set_add(fpool, v1.val.ad, v2.val.lc) ]]);
}
else
runtime("Can't add to non-[e|l]clist");
/* Community list add */
INST(FI_CLIST_ADD_PAIR, 2, 1) {
ARG(1, T_CLIST);
ARG(2, T_PAIR);
METHOD_CONSTRUCTOR("add");
RESULT(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, v2.val.i) ]]);
}
INST(FI_CLIST_DEL, 2, 1) { /* (Extended) Community list add or delete */
ARG_ANY(1);
ARG_ANY(2);
RESULT_TYPE(f1->type);
INST(FI_CLIST_ADD_IP, 2, 1) {
ARG(1, T_CLIST);
ARG(2, T_IP);
METHOD_CONSTRUCTOR("add");
if (v1.type == T_PATH)
{
if ((v2.type == T_SET) && path_set_type(v2.val.t) || (v2.type == T_INT))
RESULT_(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 0) ]]);
else
runtime("Can't delete non-integer (set)");
}
FID_NEW_BODY();
/* IP->Quad implicit conversion, must be before FI_CLIST_ADD_QUAD */
cf_warn("Method add(clist, ip) is deprecated, please use add(clist, quad)");
else if (v1.type == T_CLIST)
{
/* Community (or cluster) list */
struct f_val dummy;
if ((v2.type == T_PAIR) || (v2.type == T_QUAD))
RESULT_(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, v2.val.i) ]]);
/* IP->Quad implicit conversion */
else if (val_is_ip4(&v2))
RESULT_(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
else if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy) || (v2.type == T_CLIST))
RESULT_(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 0) ]]);
else
runtime("Can't delete non-pair");
}
else if (v1.type == T_ECLIST)
{
/* v2.val is either EC or EC-set */
if ((v2.type == T_SET) && eclist_set_type(v2.val.t) || (v2.type == T_ECLIST))
RESULT_(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
else if (v2.type != T_EC)
runtime("Can't delete non-ec");
else
RESULT_(T_ECLIST, ad, [[ ec_set_del(fpool, v1.val.ad, v2.val.ec) ]]);
}
else if (v1.type == T_LCLIST)
{
/* v2.val is either LC or LC-set */
if ((v2.type == T_SET) && lclist_set_type(v2.val.t) || (v2.type == T_LCLIST))
RESULT_(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
else if (v2.type != T_LC)
runtime("Can't delete non-lc");
else
RESULT_(T_LCLIST, ad, [[ lc_set_del(fpool, v1.val.ad, v2.val.lc) ]]);
}
else
runtime("Can't delete in non-[e|l]clist");
FID_INTERPRET_BODY();
if (!val_is_ip4(&v2)) runtime("Mismatched IP type");
RESULT(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
}
INST(FI_CLIST_FILTER, 2, 1) { /* (Extended) Community list add or delete */
ARG_ANY(1);
ARG_ANY(2);
RESULT_TYPE(f1->type);
INST(FI_CLIST_ADD_QUAD, 2, 1) {
ARG(1, T_CLIST);
ARG(2, T_QUAD);
METHOD_CONSTRUCTOR("add");
RESULT(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, v2.val.i) ]]);
}
if (v1.type == T_PATH)
{
if ((v2.type == T_SET) && path_set_type(v2.val.t))
RESULT_(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 1) ]]);
else
runtime("Can't filter integer");
}
INST(FI_CLIST_ADD_CLIST, 2, 1) {
ARG(1, T_CLIST);
ARG(2, T_CLIST);
METHOD_CONSTRUCTOR("add");
RESULT(T_CLIST, ad, [[ int_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
}
else if (v1.type == T_CLIST)
{
/* Community (or cluster) list */
struct f_val dummy;
INST(FI_ECLIST_ADD_EC, 2, 1) {
ARG(1, T_ECLIST);
ARG(2, T_EC);
METHOD_CONSTRUCTOR("add");
RESULT(T_ECLIST, ad, [[ ec_set_add(fpool, v1.val.ad, v2.val.ec) ]]);
}
if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy) || (v2.type == T_CLIST))
RESULT_(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 1) ]]);
else
runtime("Can't filter pair");
}
INST(FI_ECLIST_ADD_ECLIST, 2, 1) {
ARG(1, T_ECLIST);
ARG(2, T_ECLIST);
METHOD_CONSTRUCTOR("add");
RESULT(T_ECLIST, ad, [[ ec_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
}
else if (v1.type == T_ECLIST)
{
/* v2.val is either EC or EC-set */
if ((v2.type == T_SET) && eclist_set_type(v2.val.t) || (v2.type == T_ECLIST))
RESULT_(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
else
runtime("Can't filter ec");
}
INST(FI_LCLIST_ADD_LC, 2, 1) {
ARG(1, T_LCLIST);
ARG(2, T_LC);
METHOD_CONSTRUCTOR("add");
RESULT(T_LCLIST, ad, [[ lc_set_add(fpool, v1.val.ad, v2.val.lc) ]]);
}
else if (v1.type == T_LCLIST)
{
/* v2.val is either LC or LC-set */
if ((v2.type == T_SET) && lclist_set_type(v2.val.t) || (v2.type == T_LCLIST))
RESULT_(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
else
runtime("Can't filter lc");
}
INST(FI_LCLIST_ADD_LCLIST, 2, 1) {
ARG(1, T_LCLIST);
ARG(2, T_LCLIST);
METHOD_CONSTRUCTOR("add");
RESULT(T_LCLIST, ad, [[ lc_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
}
else
runtime("Can't filter non-[e|l]clist");
INST(FI_PATH_DELETE_INT, 2, 1) {
ARG(1, T_PATH);
ARG(2, T_INT);
METHOD_CONSTRUCTOR("delete");
RESULT(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 0) ]]);
}
INST(FI_PATH_DELETE_SET, 2, 1) {
ARG(1, T_PATH);
ARG(2, T_SET);
METHOD_CONSTRUCTOR("delete");
if (!path_set_type(v2.val.t))
runtime("Mismatched set type");
RESULT(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 0) ]]);
}
/* Community list delete */
INST(FI_CLIST_DELETE_PAIR, 2, 1) {
ARG(1, T_CLIST);
ARG(2, T_PAIR);
METHOD_CONSTRUCTOR("delete");
RESULT(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, v2.val.i) ]]);
}
INST(FI_CLIST_DELETE_IP, 2, 1) {
ARG(1, T_CLIST);
ARG(2, T_IP);
METHOD_CONSTRUCTOR("delete");
FID_NEW_BODY();
/* IP->Quad implicit conversion, must be before FI_CLIST_DELETE_QUAD */
cf_warn("Method delete(clist, ip) is deprecated, please use delete(clist, quad)");
FID_INTERPRET_BODY();
if (!val_is_ip4(&v2)) runtime("Mismatched IP type");
RESULT(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
}
INST(FI_CLIST_DELETE_QUAD, 2, 1) {
ARG(1, T_CLIST);
ARG(2, T_QUAD);
METHOD_CONSTRUCTOR("delete");
RESULT(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, v2.val.i) ]]);
}
INST(FI_CLIST_DELETE_CLIST, 2, 1) {
ARG(1, T_CLIST);
ARG(2, T_CLIST);
METHOD_CONSTRUCTOR("delete");
RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 0) ]]);
}
INST(FI_CLIST_DELETE_SET, 2, 1) {
ARG(1, T_CLIST);
ARG(2, T_SET);
METHOD_CONSTRUCTOR("delete");
if (!clist_set_type(v2.val.t, &(struct f_val){}))
runtime("Mismatched set type");
RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 0) ]]);
}
INST(FI_ECLIST_DELETE_EC, 2, 1) {
ARG(1, T_ECLIST);
ARG(2, T_EC);
METHOD_CONSTRUCTOR("delete");
RESULT(T_ECLIST, ad, [[ ec_set_del(fpool, v1.val.ad, v2.val.ec) ]]);
}
INST(FI_ECLIST_DELETE_ECLIST, 2, 1) {
ARG(1, T_ECLIST);
ARG(2, T_ECLIST);
METHOD_CONSTRUCTOR("delete");
RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
}
INST(FI_ECLIST_DELETE_SET, 2, 1) {
ARG(1, T_ECLIST);
ARG(2, T_SET);
METHOD_CONSTRUCTOR("delete");
if (!eclist_set_type(v2.val.t))
runtime("Mismatched set type");
RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
}
INST(FI_LCLIST_DELETE_LC, 2, 1) {
ARG(1, T_LCLIST);
ARG(2, T_LC);
METHOD_CONSTRUCTOR("delete");
RESULT(T_LCLIST, ad, [[ lc_set_del(fpool, v1.val.ad, v2.val.lc) ]]);
}
INST(FI_LCLIST_DELETE_LCLIST, 2, 1) {
ARG(1, T_LCLIST);
ARG(2, T_LCLIST);
METHOD_CONSTRUCTOR("delete");
RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
}
INST(FI_LCLIST_DELETE_SET, 2, 1) {
ARG(1, T_LCLIST);
ARG(2, T_SET);
METHOD_CONSTRUCTOR("delete");
if (!lclist_set_type(v2.val.t))
runtime("Mismatched set type");
RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
}
INST(FI_PATH_FILTER_SET, 2, 1) {
ARG(1, T_PATH);
ARG(2, T_SET);
METHOD_CONSTRUCTOR("filter");
if (!path_set_type(v2.val.t))
runtime("Mismatched set type");
RESULT(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 1) ]]);
}
INST(FI_CLIST_FILTER_CLIST, 2, 1) {
ARG(1, T_CLIST);
ARG(2, T_CLIST);
METHOD_CONSTRUCTOR("filter");
RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 1) ]]);
}
INST(FI_CLIST_FILTER_SET, 2, 1) {
ARG(1, T_CLIST);
ARG(2, T_SET);
METHOD_CONSTRUCTOR("filter");
if (!clist_set_type(v2.val.t, &(struct f_val){}))
runtime("Mismatched set type");
RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 1) ]]);
}
INST(FI_ECLIST_FILTER_ECLIST, 2, 1) {
ARG(1, T_ECLIST);
ARG(2, T_ECLIST);
METHOD_CONSTRUCTOR("filter");
RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
}
INST(FI_ECLIST_FILTER_SET, 2, 1) {
ARG(1, T_ECLIST);
ARG(2, T_SET);
METHOD_CONSTRUCTOR("filter");
if (!eclist_set_type(v2.val.t))
runtime("Mismatched set type");
RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
}
INST(FI_LCLIST_FILTER_LCLIST, 2, 1) {
ARG(1, T_LCLIST);
ARG(2, T_LCLIST);
METHOD_CONSTRUCTOR("filter");
RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
}
INST(FI_LCLIST_FILTER_SET, 2, 1) {
ARG(1, T_LCLIST);
ARG(2, T_SET);
METHOD_CONSTRUCTOR("filter");
if (!lclist_set_type(v2.val.t))
runtime("Mismatched set type");
RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
}
INST(FI_ROA_CHECK, 2, 1) { /* ROA Check */
@ -1501,6 +1507,21 @@
}
INST(FI_FROM_HEX, 1, 1) { /* Convert hex text to bytestring */
ARG(1, T_STRING);
int len = bstrhextobin(v1.val.s, NULL);
if (len < 0)
runtime("Invalid hex string");
struct adata *bs;
bs = falloc(sizeof(struct adata) + len);
bs->length = bstrhextobin(v1.val.s, bs->data);
ASSERT(bs->length == (size_t) len);
RESULT(T_BYTESTRING, bs, bs);
}
INST(FI_FORMAT, 1, 1) { /* Format */
ARG_ANY(1);
RESULT(T_STRING, s, val_format_str(fpool, &v1));

View File

@ -19,6 +19,7 @@
#include "filter/data.h"
#include "lib/buffer.h"
#include "lib/flowspec.h"
#include "lib/string.h"
/* Flags for instructions */
enum f_instruction_flags {
@ -35,6 +36,16 @@ const char *f_instruction_name_(enum f_instruction_code fi);
static inline const char *f_instruction_name(enum f_instruction_code fi)
{ return f_instruction_name_(fi) + 3; }
int f_const_promotion_(struct f_inst *arg, enum btype want, int update);
static inline int f_const_promotion(struct f_inst *arg, enum btype want)
{ return f_const_promotion_(arg, want, 1); }
static inline int f_try_const_promotion(struct f_inst *arg, enum btype want)
{ return f_const_promotion_(arg, want, 0); }
struct f_arg {
struct symbol *arg;
struct f_arg *next;
@ -47,6 +58,7 @@ struct f_line {
u8 args; /* Function: Args required */
u8 vars;
u8 results; /* Results left on stack: cmd -> 0, term -> 1 */
u8 return_type; /* Type which the function returns */
struct f_arg *arg_list;
struct f_line_item items[0]; /* The items themselves */
};
@ -94,16 +106,16 @@ void f_add_lines(const struct f_line_item *what, struct filter_iterator *fit);
struct filter *f_new_where(struct f_inst *);
struct f_inst *f_dispatch_method(struct symbol *sym, struct f_inst *obj, struct f_inst *args, int skip);
struct f_inst *f_dispatch_method_x(const char *name, enum btype t, struct f_inst *obj, struct f_inst *args);
struct f_inst *f_for_cycle(struct symbol *var, struct f_inst *term, struct f_inst *block);
struct f_inst *f_implicit_roa_check(struct rtable_config *tab);
struct f_inst *f_print(struct f_inst *vars, int flush, enum filter_return fret);
static inline struct f_static_attr f_new_static_attr(btype type, int code, int readonly)
{ return (struct f_static_attr) { .type = type, .sa_code = code, .readonly = readonly }; }
struct f_inst *f_generate_roa_check(struct rtable_config *table, struct f_inst *prefix, struct f_inst *asn);
struct f_attr_bit {
const struct ea_class *class;
uint bit;
};
#define f_new_dynamic_attr_bit(_bit, _name) ((struct f_attr_bit) { .bit = _bit, .class = ea_class_find(_name) })
/* Hook for call bt_assert() function in configuration */
extern void (*bt_assert_hook)(int result, const struct f_line_item *assert);

View File

@ -13,7 +13,7 @@
#include "filter/f-inst.h"
#include "lib/idm.h"
#include "nest/protocol.h"
#include "nest/rt.h"
#include "nest/route.h"
#define P(a,b) ((a<<8) | b)
@ -30,7 +30,8 @@ filter_name(const struct filter *filter)
return filter->sym->name;
}
struct filter *f_new_where(struct f_inst *where)
struct filter *
f_new_where(struct f_inst *where)
{
struct f_inst *cond = f_new_inst(FI_CONDITION, where,
f_new_inst(FI_DIE, F_ACCEPT),
@ -40,3 +41,195 @@ struct filter *f_new_where(struct f_inst *where)
f->root = f_linearize(cond, 0);
return f;
}
static inline int
f_match_signature(const struct f_method *dsc, struct f_inst *args)
{
int i, arg_num = (int) dsc->arg_num;
for (i = 1; args && (i < arg_num); args = args->next, i++)
if (dsc->args_type[i] && (args->type != dsc->args_type[i]) &&
!f_try_const_promotion(args, dsc->args_type[i]))
return 0;
return !args && !(i < arg_num);
}
/* Variant of f_match_signature(), optimized for error reporting */
static inline void
f_match_signature_err(const struct f_method *dsc, struct f_inst *args, int *pos, int *want, int *got)
{
int i, arg_num = (int) dsc->arg_num;
for (i = 1; args && (i < arg_num); args = args->next, i++)
if (dsc->args_type[i] && (args->type != dsc->args_type[i]) &&
!f_try_const_promotion(args, dsc->args_type[i]))
break;
*pos = i;
*want = (i < arg_num) ? dsc->args_type[i] : T_NONE;
*got = args ? args->type : T_NONE;
}
struct f_inst *
f_dispatch_method(struct symbol *sym, struct f_inst *obj, struct f_inst *args, int skip)
{
/* Find match */
for (const struct f_method *dsc = sym->method; dsc; dsc = dsc->next)
if (f_match_signature(dsc, args))
return dsc->new_inst(obj, args);
/* No valid match - format error message */
int best_pos = -1; /* Longest argument position with partial match */
int best_got = 0; /* Received type at best partial match position */
int best_count = 0; /* Number of partial matches at best position */
const int best_max = 8; /* Max number of reported types */
int best_want[best_max]; /* Expected types at best position */
for (const struct f_method *dsc = sym->method; dsc; dsc = dsc->next)
{
int pos, want, got;
f_match_signature_err(dsc, args, &pos, &want, &got);
/* Ignore shorter match */
if (pos < best_pos)
continue;
/* Found longer match, reset existing results */
if (pos > best_pos)
{
best_pos = pos;
best_got = got;
best_count = 0;
}
/* Skip duplicates */
for (int i = 0; i < best_count; i++)
if (best_want[i] == want)
goto next;
/* Skip if we have enough types */
if (best_count >= best_max)
continue;
/* Add new expected type */
best_want[best_count] = want;
best_count++;
next:;
}
/* There is at least one method */
ASSERT(best_pos >= 0 && best_count > 0);
/* Update best_pos for printing */
best_pos = best_pos - skip + 1;
if (!best_got)
cf_error("Cannot infer type of argument %d of '%s', please assign it to a variable", best_pos, sym->name);
/* Format list of expected types */
buffer tbuf;
STACK_BUFFER_INIT(tbuf, 128);
for (int i = 0; i < best_count; i++)
buffer_print(&tbuf, " / %s", best_want[i] ? f_type_name(best_want[i]) : "any");
char *types = tbuf.start + 3;
char *dots = (best_count >= best_max) || (tbuf.pos == tbuf.end) ? " / ..." : "";
cf_error("Argument %d of '%s' expected %s%s, got %s",
best_pos, sym->name, types, dots, f_type_name(best_got));
}
struct f_inst *
f_dispatch_method_x(const char *name, enum btype t, struct f_inst *obj, struct f_inst *args)
{
struct sym_scope *scope = f_type_method_scope(t);
struct symbol *sym = cf_find_symbol_scope(scope, name);
if (!sym)
cf_error("Cannot dispatch method '%s'", name);
return f_dispatch_method(sym, obj, args, 0);
}
struct f_inst *
f_for_cycle(struct symbol *var, struct f_inst *term, struct f_inst *block)
{
ASSERT((var->class & ~0xff) == SYM_VARIABLE);
ASSERT(term->next == NULL);
/* Static type check */
if (term->type == T_VOID)
cf_error("Cannot infer type of FOR expression, please assign it to a variable");
enum btype el_type = f_type_element_type(term->type);
struct sym_scope *scope = el_type ? f_type_method_scope(term->type) : NULL;
struct symbol *ms = scope ? cf_find_symbol_scope(scope, "!for_next") : NULL;
if (!ms)
cf_error("Type %s is not iterable, can't be used in FOR", f_type_name(term->type));
if (var->class != (SYM_VARIABLE | el_type))
cf_error("Loop variable '%s' in FOR must be of type %s, got %s",
var->name, f_type_name(el_type), f_type_name(var->class & 0xff));
/* Push the iterator auxiliary value onto stack */
struct f_inst *iter = term->next = f_new_inst(FI_CONSTANT, (struct f_val) {});
/* Initialize the iterator variable */
iter->next = f_new_inst(FI_CONSTANT, (struct f_val) { .type = el_type });
/* Prepend the loop block with loop beginning instruction */
struct f_inst *loop_start = f_new_inst(FI_FOR_LOOP_START, var);
loop_start->next = block;
return ms->method->new_inst(term, loop_start);
}
struct f_inst *
f_implicit_roa_check(struct rtable_config *tab)
{
const struct ea_class *def = ea_class_find("bgp_path");
if (!def)
bug("Couldn't find BGP AS Path attribute definition.");
struct f_inst *path_getter = f_new_inst(FI_EA_GET, f_new_inst(FI_CURRENT_ROUTE), def);
struct sym_scope *scope = f_type_method_scope(path_getter->type);
struct symbol *ms = scope ? cf_find_symbol_scope(scope, "last") : NULL;
if (!ms)
bug("Couldn't find the \"last\" method for AS Path.");
struct f_static_attr fsa = f_new_static_attr(T_NET, SA_NET, 1);
return f_new_inst(FI_ROA_CHECK,
f_new_inst(FI_RTA_GET, f_new_inst(FI_CURRENT_ROUTE), fsa),
ms->method->new_inst(path_getter, NULL),
tab);
}
struct f_inst *
f_print(struct f_inst *vars, int flush, enum filter_return fret)
{
#define AX(...) do { struct f_inst *_tmp = f_new_inst(__VA_ARGS__); _tmp->next = output; output = _tmp; } while (0)
struct f_inst *output = NULL;
if (fret != F_NOP)
AX(FI_DIE, fret);
if (flush)
AX(FI_FLUSH);
while (vars)
{
struct f_inst *tmp = vars;
vars = vars->next;
tmp->next = NULL;
AX(FI_PRINT, tmp);
}
return output;
#undef AX
}

View File

@ -35,7 +35,7 @@
#include "lib/ip.h"
#include "lib/net.h"
#include "lib/flowspec.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
#include "lib/attrs.h"
@ -76,6 +76,9 @@ struct filter_state {
/* The route we are processing. This may be NULL to indicate no route available. */
struct rte *rte;
/* Additional external values provided to the filter */
const struct f_val *val;
/* Buffer for log output */
log_buffer buf;
@ -109,18 +112,20 @@ static struct tbf rl_runtime_err = TBF_DEFAULT_LOG_LIMITS;
* TWOARGS macro to get both of them evaluated.
*/
static enum filter_return
interpret(struct filter_state *fs, const struct f_line *line, struct f_val *val)
interpret(struct filter_state *fs, const struct f_line *line, uint argc, const struct f_val *argv, uint resc, struct f_val *resv)
{
/* No arguments allowed */
ASSERT(line->args == 0);
/* Check of appropriate number of arguments */
ASSERT(line->args == argc);
/* Initialize the filter stack */
struct filter_stack *fstk = &fs->stack;
fstk->vcnt = line->vars;
memset(fstk->vstk, 0, sizeof(struct f_val) * line->vars);
/* Set the arguments and top-level variables */
fstk->vcnt = line->vars + line->args;
memcpy(fstk->vstk, argv, sizeof(struct f_val) * line->args);
memset(fstk->vstk + argc, 0, sizeof(struct f_val) * line->vars);
/* The same as with the value stack. Not resetting the stack for performance reasons. */
/* The same as with the value stack. Not resetting the stack completely for performance reasons. */
fstk->ecnt = 1;
fstk->estk[0] = (struct filter_exec_stack) {
.line = line,
@ -128,6 +133,7 @@ interpret(struct filter_state *fs, const struct f_line *line, struct f_val *val)
};
#define curline fstk->estk[fstk->ecnt-1]
#define prevline fstk->estk[fstk->ecnt-2]
#ifdef LOCAL_DEBUG
debug("Interpreting line.");
@ -172,21 +178,14 @@ interpret(struct filter_state *fs, const struct f_line *line, struct f_val *val)
fstk->ecnt--;
}
if (fstk->vcnt == 0) {
if (val) {
log_rl(&rl_runtime_err, L_ERR "filters: No value left on stack");
return F_ERROR;
}
return F_NOP;
if (fstk->vcnt != resc)
{
log_rl(&rl_runtime_err, L_ERR "Filter expected to leave %d values on stack but %d left instead", resc, fstk->vcnt);
return F_ERROR;
}
if (val && (fstk->vcnt == 1)) {
*val = fstk->vstk[0];
return F_NOP;
}
log_rl(&rl_runtime_err, L_ERR "Too many items left on stack: %u", fstk->vcnt);
return F_ERROR;
memcpy(resv, fstk->vstk, sizeof(struct f_val) * resc);
return F_NOP;
}
@ -209,6 +208,12 @@ f_run(const struct filter *filter, struct rte *rte, int flags)
if (filter == FILTER_REJECT)
return F_REJECT;
return f_run_args(filter, rte, 0, NULL, flags);
}
enum filter_return
f_run_args(const struct filter *filter, struct rte *rte, uint argc, const struct f_val *argv, int flags)
{
DBG( "Running filter `%s'...", filter->name );
/* Initialize the filter state */
@ -220,7 +225,7 @@ f_run(const struct filter *filter, struct rte *rte, int flags)
f_stack_init(filter_state);
/* Run the interpreter itself */
enum filter_return fret = interpret(&filter_state, filter->root, NULL);
enum filter_return fret = interpret(&filter_state, filter->root, argc, argv, 0, NULL);
/* Process the filter output, log it and return */
if (fret < F_ACCEPT) {
@ -246,7 +251,7 @@ f_run(const struct filter *filter, struct rte *rte, int flags)
*/
enum filter_return
f_eval_rte(const struct f_line *expr, struct rte *rte)
f_eval_rte(const struct f_line *expr, struct rte *rte, uint argc, const struct f_val *argv, uint resc, struct f_val *resv)
{
filter_state = (struct filter_state) {
.rte = rte,
@ -254,16 +259,14 @@ f_eval_rte(const struct f_line *expr, struct rte *rte)
f_stack_init(filter_state);
ASSERT(!rta_is_cached(rte->attrs));
return interpret(&filter_state, expr, NULL);
return interpret(&filter_state, expr, argc, argv, resc, resv);
}
/*
* f_eval - get a value of a term
* @expr: filter line containing the term
* @tmp_pool: long data may get allocated from this pool
* @pres: here the output will be stored
* @pres: here the output will be stored if requested
*/
enum filter_return
f_eval(const struct f_line *expr, struct f_val *pres)
@ -272,34 +275,28 @@ f_eval(const struct f_line *expr, struct f_val *pres)
f_stack_init(filter_state);
enum filter_return fret = interpret(&filter_state, expr, pres);
enum filter_return fret = interpret(&filter_state, expr, 0, NULL, !!pres, pres);
return fret;
}
/*
* f_eval_int - get an integer value of a term
* Called internally from the config parser, uses its internal memory pool
* for allocations. Do not call in other cases.
* cf_eval_tmp - evaluate a value of a term and check its type
*/
uint
f_eval_int(const struct f_line *expr)
struct f_val
cf_eval_tmp(const struct f_inst *inst, int type)
{
/* Called independently in parse-time to eval expressions */
filter_state = (struct filter_state) {};
f_stack_init(filter_state);
struct f_val val;
if (interpret(&filter_state, expr, &val) > F_RETURN)
if (f_eval(f_linearize(inst, 1), &val) > F_RETURN)
cf_error("Runtime error while evaluating expression; see log for details");
if (val.type != T_INT)
cf_error("Integer expression expected");
if (type != T_VOID && val.type != type)
cf_error("Expression of type %s expected", f_type_name(type));
return val.val.i;
return val;
}
/*
* f_eval_buf - get a value of a term and print it to the supplied buffer
*/

View File

@ -13,8 +13,10 @@
#include "lib/resource.h"
#include "lib/ip.h"
#include "lib/macro.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "lib/attrs.h"
#include "filter/data.h"
#include "conf/conf.h"
/* Possible return values of filter execution */
enum filter_return {
@ -40,9 +42,8 @@ static inline const char *filter_return_str(const enum filter_return fret) {
}
}
struct f_val;
/* The filter encapsulating structure to be pointed-to from outside */
struct f_inst;
struct f_line;
struct filter {
struct symbol *sym;
@ -52,10 +53,19 @@ struct filter {
struct rte;
enum filter_return f_run(const struct filter *filter, struct rte *rte, int flags);
enum filter_return f_eval_rte(const struct f_line *expr, struct rte *rte);
uint f_eval_int(const struct f_line *expr);
enum filter_return f_run_args(const struct filter *filter, struct rte *rte, uint argc, const struct f_val *argv, int flags);
enum filter_return f_eval_rte(const struct f_line *expr, struct rte *rte, uint argc, const struct f_val *argv, uint resc, struct f_val *resv);
enum filter_return f_eval_buf(const struct f_line *expr, buffer *buf);
struct f_val cf_eval_tmp(const struct f_inst *inst, int type);
static inline struct f_val *cf_eval(const struct f_inst *inst, int type)
{
struct f_val val = cf_eval_tmp(inst, type);
return lp_val_copy(cfg_mem, &val);
}
static inline uint cf_eval_int(const struct f_inst *inst) { return cf_eval_tmp(inst, T_INT).val.i; };
const char *filter_name(const struct filter *filter);
int filter_same(const struct filter *new, const struct filter *old);
int f_same(const struct f_line *f1, const struct f_line *f2);

View File

@ -25,9 +25,9 @@
static int
t_reconfig(void)
t_reconfig(const void *arg)
{
if (!bt_config_file_parse(BT_CONFIG_FILE))
if (!bt_config_file_parse(arg))
return 0;
struct symbol *s;
@ -79,7 +79,9 @@ main(int argc, char *argv[])
if (!bt_config_file_parse(BT_CONFIG_FILE))
abort();
bt_test_suite_extra(t_reconfig, 0, BT_TIMEOUT, "Testing reconfiguration");
bt_test_suite_arg_extra(t_reconfig, BT_CONFIG_FILE ".overlay", 0, BT_TIMEOUT, "Testing reconfiguration to overlay");
bt_test_suite_arg_extra(t_reconfig, BT_CONFIG_FILE, 0, BT_TIMEOUT, "Testing reconfiguration back");
bt_test_suite_arg_extra(t_reconfig, BT_CONFIG_FILE, 0, BT_TIMEOUT, "Testing reconfiguration to the same file");
struct f_bt_test_suite *t;
WALK_LIST(t, config->tests)

View File

@ -9,6 +9,9 @@ router id 62.168.0.1;
/* We have to setup any protocol */
protocol device { }
attribute bgppath mypath;
attribute lclist mylclist;
/* Setting some custom attributes, enough to force BIRD to reallocate the attribute idmap */
attribute int test_ca_int1;
attribute int test_ca_int2;
@ -121,17 +124,17 @@ attribute lclist test_ca_lclist_max21;
define one = 1;
define ten = 10;
function onef(int a)
function onef(int a) -> int
{
return 1;
}
function twof(int a)
function twof(int a) -> int
{
return 2;
}
function oneg(int a)
function oneg(int a) -> int
{
return 1;
}
@ -140,6 +143,18 @@ bt_test_same(onef, onef, 1);
bt_test_same(onef, oneg, 1);
bt_test_same(onef, twof, 0);
/*
* Testing filter corner cases
* ---------------------------
*/
function t_nothing() {}
bt_test_suite(t_nothing, "Testing nothing");
function t_metanothing() { t_nothing(); }
bt_test_suite(t_metanothing, "Testing meta nothing");
/*
* Testing boolean expressions
* ---------------------------
@ -178,6 +193,14 @@ bt_test_suite(t_bool, "Testing boolean expressions");
* ----------------
*/
function aux_t_int(int t; int u)
{
case t {
1: {}
else: {}
}
}
define four = 4;
define xyzzy = (120+10);
define '1a-a1' = (xyzzy-100);
@ -230,7 +253,10 @@ function t_int()
else: bt_assert(false);
}
aux_t_int(1, 2);
aux_t_int(1, 3);
aux_t_int(2, 3);
aux_t_int(2, 2);
}
bt_test_suite(t_int, "Testing integers");
@ -324,12 +350,42 @@ bt_test_suite(t_string, "Testing string matching");
/*
* Testing bytestings
* ------------------
*/
function t_bytestring()
{
bytestring bs1 = hex:;
bytestring bs2 = hex:0112233445566778899aabbccddeeff0;
bt_assert(format(bs1) = "");
bt_assert(format(bs2) = "01:12:23:34:45:56:67:78:89:9a:ab:bc:cd:de:ef:f0");
bt_assert(hex:01:12:23:34:45:56:67:78:89:9a:ab:bc:cd:de:ef:f0 = bs2);
bt_assert(01:12:23:34:45:56:67:78:89:9a:ab:bc:cd:de:ef:f0 = bs2);
bt_assert(0112233445566778899aabbccddeeff0 = bs2);
bt_assert(hex:01234567 = hex:01:23:45:67);
bt_assert(hex:0123456789abcdef != bs2);
bt_assert(hex:0123456789abcdef != hex:0123);
bt_assert(format(hex:0123456789abcdef) = "01:23:45:67:89:ab:cd:ef");
bt_assert(from_hex(" ") = bs1);
bt_assert(from_hex("01:12:23:34:45:56:67:78:89:9a:ab:bc:cd:de:ef:f0") = bs2);
bt_assert(from_hex(format(bs2)) = bs2);
bt_assert(from_hex(" 0112:23-34455667 78-89 - 9a-ab bc:cd : de:eff0 ") = bs2);
}
bt_test_suite(t_bytestring, "Testing bytestrings");
/*
* Testing pairs
* -------------
*/
function 'mkpair-a'(int a)
function 'mkpair-a'(int a) -> pair
{
return (1, a);
}
@ -652,10 +708,10 @@ prefix set pxs;
bt_assert(format([ 0.0.0.0/0 ]) = "[0.0.0.0/0]");
bt_assert(format([ 10.10.0.0/32 ]) = "[10.10.0.0/32{0.0.0.1}]");
bt_assert(format([ 10.10.0.0/17 ]) = "[10.10.0.0/17{0.0.128.0}]");
bt_assert(format([ 10.10.0.0/17{17,19} ]) = "[10.10.0.0/17{0.0.224.0}]"); # 224 = 128+64+32
# bt_assert(format([ 10.10.0.0/17{17,19} ]) = "[10.10.0.0/17{0.0.224.0}]"); # 224 = 128+64+32
bt_assert(format([ 10.10.128.0/17{18,19} ]) = "[10.10.128.0/18{0.0.96.0}, 10.10.192.0/18{0.0.96.0}]"); # 96 = 64+32
bt_assert(format([ 10.10.64.0/18- ]) = "[0.0.0.0/0, 0.0.0.0/1{128.0.0.0}, 0.0.0.0/2{64.0.0.0}, 0.0.0.0/3{32.0.0.0}, 10.10.0.0/16{255.255.0.0}, 10.10.0.0/17{0.0.128.0}, 10.10.64.0/18{0.0.64.0}]");
bt_assert(format([ 10.10.64.0/18+ ]) = "[10.10.64.0/18{0.0.96.0}, 10.10.64.0/20{0.0.31.255}, 10.10.80.0/20{0.0.31.255}, 10.10.96.0/20{0.0.31.255}, 10.10.112.0/20{0.0.31.255}]");
# bt_assert(format([ 10.10.64.0/18- ]) = "[0.0.0.0/0, 0.0.0.0/1{128.0.0.0}, 0.0.0.0/2{64.0.0.0}, 0.0.0.0/3{32.0.0.0}, 10.10.0.0/16{255.255.0.0}, 10.10.0.0/17{0.0.128.0}, 10.10.64.0/18{0.0.64.0}]");
# bt_assert(format([ 10.10.64.0/18+ ]) = "[10.10.64.0/18{0.0.96.0}, 10.10.64.0/20{0.0.31.255}, 10.10.80.0/20{0.0.31.255}, 10.10.96.0/20{0.0.31.255}, 10.10.112.0/20{0.0.31.255}]");
bt_assert(format([ 10.10.160.0/19 ]) = "[10.10.160.0/19{0.0.32.0}]");
bt_assert(format([ 10.10.160.0/19{19,22} ]) = "[10.10.160.0/19{0.0.32.0}, 10.10.160.0/20{0.0.28.0}, 10.10.176.0/20{0.0.28.0}]"); # 28 = 16+8+4
@ -667,10 +723,10 @@ prefix set pxs;
bt_assert(format([ 11:22:33:44::/64+ ]) = "[11:22:33:44::/64{::1:ffff:ffff:ffff:ffff}]");
bt_assert(format([ 11:22:33:44::/65 ]) = "[11:22:33:44::/65{::8000:0:0:0}]");
bt_assert(format([ 11:22:33:44::/65{65,67} ]) = "[11:22:33:44::/65{::e000:0:0:0}]"); # e = 8+4+2
# bt_assert(format([ 11:22:33:44::/65{65,67} ]) = "[11:22:33:44::/65{::e000:0:0:0}]"); # e = 8+4+2
bt_assert(format([ 11:22:33:44:8000::/65{66,67} ]) = "[11:22:33:44:8000::/66{::6000:0:0:0}, 11:22:33:44:c000::/66{::6000:0:0:0}]"); # 6 = 4+2
bt_assert(format([ 11:22:33:44:4000::/66- ]) = "[::/0, ::/1{8000::}, ::/2{4000::}, ::/3{2000::}, 11:22:33:44::/64{ffff:ffff:ffff:ffff::}, 11:22:33:44::/65{::8000:0:0:0}, 11:22:33:44:4000::/66{::4000:0:0:0}]");
bt_assert(format([ 11:22:33:44:4000::/66+ ]) = "[11:22:33:44:4000::/66{::6000:0:0:0}, 11:22:33:44:4000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:5000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:6000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:7000::/68{::1fff:ffff:ffff:ffff}]");
# bt_assert(format([ 11:22:33:44:4000::/66- ]) = "[::/0, ::/1{8000::}, ::/2{4000::}, ::/3{2000::}, 11:22:33:44::/64{ffff:ffff:ffff:ffff::}, 11:22:33:44::/65{::8000:0:0:0}, 11:22:33:44:4000::/66{::4000:0:0:0}]");
# bt_assert(format([ 11:22:33:44:4000::/66+ ]) = "[11:22:33:44:4000::/66{::6000:0:0:0}, 11:22:33:44:4000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:5000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:6000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:7000::/68{::1fff:ffff:ffff:ffff}]");
bt_assert(format([ 11:22:33:44:c000::/67 ]) = "[11:22:33:44:c000::/67{::2000:0:0:0}]");
bt_assert(format([ 11:22:33:44:c000::/67{67,71} ]) = "[11:22:33:44:c000::/67{::2000:0:0:0}, 11:22:33:44:c000::/68{::1e00:0:0:0}, 11:22:33:44:d000::/68{::1e00:0:0:0}]");
bt_assert(format([ 11:22:33:44:c000::/67+ ]) = "[11:22:33:44:c000::/67{::2000:0:0:0}, 11:22:33:44:c000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:d000::/68{::1fff:ffff:ffff:ffff}]");
@ -687,8 +743,8 @@ bt_test_suite(t_prefix_set, "Testing prefix sets");
*/
function t_prefix6()
prefix px;
{
prefix px;
px = 1020::/18;
bt_assert(format(px) = "1020::/18");
bt_assert(1020:3040:5060:: ~ 1020:3040:5000::/40);
@ -804,14 +860,14 @@ bt_test_suite(t_flowspec, "Testing flowspec routes");
* -------------
*/
function mkpath(int a; int b)
function mkpath(int a; int b) -> bgpmask
{
return [= a b 3 2 1 =];
}
define set35 = [3 .. 5];
function t_path()
function t_path_old()
bgpmask pm1;
bgppath p2;
int set set12;
@ -890,7 +946,91 @@ int set set12;
bt_assert(x = 18 && y = 50);
}
bt_test_suite(t_path, "Testing paths");
bt_test_suite(t_path_old, "Testing paths (old syntax)");
function t_path_new()
{
bgpmask pm1 = [= 4 3 2 1 =];
int set set12 = [1, 2];
bt_assert(format(pm1) = "[= 4 3 2 1 =]");
bt_assert(+empty+ = +empty+);
bt_assert(10 !~ +empty+);
bgppath p2;
bt_assert(p2 = +empty+);
p2.prepend(1);
p2.prepend(2);
p2.prepend(3);
p2.prepend(4);
bt_assert(p2.empty = +empty+);
bt_assert(format(p2) = "(path 4 3 2 1)");
bt_assert(p2.len = 4);
bt_assert(p2 ~ pm1);
bt_assert(3 ~ p2);
bt_assert(p2 ~ [2, 10..20]);
bt_assert(p2 ~ [4, 10..20]);
bt_assert(p2 !~ []);
p2.prepend(5);
bt_assert(p2 !~ pm1);
bt_assert(10 !~ p2);
bt_assert(p2 !~ [8, ten..(2*ten)]);
bt_assert(p2 ~ [= * 4 3 * 1 =]);
bt_assert(p2 ~ [= (3+2) (2*2) 3 2 1 =]);
bt_assert(p2 ~ [= 5 [2, 4, 6] 3 [1..2] 1 =]);
bt_assert(p2 ~ [= 5 set35 3 set12 set12 =]);
bt_assert(p2 ~ mkpath(5, 4));
bt_assert(p2 ~ [= * [3] * =]);
bt_assert(p2 !~ [= * [] * =]);
bt_assert(p2.len = 5);
bt_assert(p2.first = 5);
bt_assert(p2.last = 1);
bt_assert(p2.len = 5);
bt_assert(p2.delete(3) = +empty+.prepend(1).prepend(2).prepend(4).prepend(5));
bt_assert(p2.filter([1..3]) = +empty+.prepend(1).prepend(2).prepend(3));
bt_assert(p2.delete([]) = p2);
bt_assert(p2.filter([]) = +empty+);
bt_assert(+empty+.prepend(0).prepend(1).delete([]) = +empty+.prepend(0).prepend(1));
bt_assert(+empty+.prepend(0).prepend(1).filter([]) = +empty+);
p2 = +empty+;
p2.prepend(5);
p2.prepend(4);
p2.prepend(3);
p2.prepend(3);
p2.prepend(2);
p2.prepend(1);
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(p2.delete(3) = +empty+.prepend(5).prepend(4).prepend(2).prepend(1));
bt_assert(p2.delete([4..5]) = +empty+.prepend(3).prepend(3).prepend(2).prepend(1));
bt_assert(format([= 1 2+ 3 =]) = "[= 1 2 + 3 =]");
# iteration over path
int x = 0;
int y = 0;
for int i in p2 do {
x = x + i;
y = y + x;
}
bt_assert(x = 18 && y = 50);
}
bt_test_suite(t_path_new, "Testing paths (new syntax)");
@ -902,7 +1042,7 @@ bt_test_suite(t_path, "Testing paths");
define p23 = (2, 3);
function t_clist()
function t_clist_old()
clist l;
clist l2;
clist r;
@ -981,6 +1121,12 @@ clist r;
l = filter(l2, [(3,1..4)]);
l2 = filter(l2, [(3,3..6)]);
quad q = 2.0.1.0;
clist ql = add(add(add(-empty-, 1.0.0.1), q), 3.1.0.0);
bt_assert(delete(ql, 1.0.0.1) = add(add(-empty-, 2.0.1.0), 3.1.0.0));
bt_assert(delete(ql, [2.0.0.0 .. 4.0.0.0]) = add(-empty-, 1.0.0.1));
bt_assert(filter(ql, [3.0.0.0 .. 4.0.0.0]) = add(-empty-, 3.1.0.0));
# clist A (10,20,30)
bt_assert(l = add(add(add(add(-empty-, (3,1)), (3,2)), (3,3)), (3,4)));
bt_assert(format(l) = "(clist (3,1) (3,2) (3,3) (3,4))");
@ -1022,7 +1168,135 @@ clist r;
bt_assert(x = 36);
}
bt_test_suite(t_clist, "Testing lists of communities");
bt_test_suite(t_clist_old, "Testing lists of communities (old syntax)");
function t_clist_new()
{
bt_assert((10, 20).asn = 10);
bt_assert((10, 20).data = 20);
bt_assert(p23.asn = 2);
bt_assert(p23.data = 3);
clist l;
bt_assert(l = -empty-);
bt_assert(l !~ [(*,*)]);
bt_assert((l ~ [(*,*)]) != (l !~ [(*,*)]));
bt_assert(-empty- = -empty-);
l.add( (one,2) );
bt_assert(l ~ [(*,*)]);
l.add( (2,one+2) );
bt_assert(format(l) = "(clist (1,2) (2,3))");
bt_assert(l.empty = -empty-);
bt_assert((2,3) ~ l);
bt_assert(l ~ [(1,*)]);
bt_assert(l ~ [p23]);
bt_assert(l ~ [(2,2..3)]);
bt_assert(l ~ [(1,1..2)]);
bt_assert(l ~ [(1,1)..(1,2)]);
bt_assert(l !~ []);
l.add((2,5));
l.add((5,one));
l.add((6,one));
l.add((one,one));
l.delete([(5,1),(6,one),(one,1)]);
l.delete([(5,one),(6,one)]);
l.filter([(1,*)]);
bt_assert(l = -empty-.add((1,2)));
bt_assert((2,3) !~ l);
bt_assert(l !~ [(2,*)]);
bt_assert(l !~ [(one,3..6)]);
bt_assert(l ~ [(*,*)]);
l.add((3,one));
l.add((one+one+one,one+one));
l.add((3,3));
l.add((3,4));
l.add((3,5));
clist l2 = l.filter([(3,*)]);
l.delete([(3,2..4)]);
bt_assert(l = -empty-.add((1,2)).add((3,1)).add((3,5)));
bt_assert(l.len = 3);
l.add((3,2));
l.add((4,5));
bt_assert(l = -empty-.add((1,2)).add((3,1)).add((3,5)).add((3,2)).add((4,5)));
bt_assert(l.len = 5);
bt_assert(l ~ [(*,2)]);
bt_assert(l ~ [(*,5)]);
bt_assert(l ~ [(*, one)]);
bt_assert(l !~ [(*,3)]);
bt_assert(l !~ [(*,(one+6))]);
bt_assert(l !~ [(*, (one+one+one))]);
bt_assert(l.delete([]) = l);
bt_assert(l.filter([]) = -empty-);
l.delete([(*,(one+onef(3)))]);
l.delete([(*,(4+one))]);
bt_assert(l = -empty-.add((3,1)));
l.delete([(*,(onef(5)))]);
bt_assert(l = -empty-);
l2.add((3,6));
l = l2.filter([(3,1..4)]);
l2.filter([(3,3..6)]);
quad q = 2.0.1.0;
clist ql = -empty-.add(1.0.0.1).add(q).add(3.1.0.0);
bt_assert(delete(ql, 1.0.0.1) = -empty-.add(2.0.1.0).add(3.1.0.0));
bt_assert(delete(ql, [2.0.0.0 .. 4.0.0.0]) = -empty-.add(1.0.0.1));
bt_assert(filter(ql, [3.0.0.0 .. 4.0.0.0]) = -empty-.add(3.1.0.0));
# clist A (10,20,30)
bt_assert(l = -empty-.add((3,1)).add((3,2)).add((3,3)).add((3,4)));
bt_assert(format(l) = "(clist (3,1) (3,2) (3,3) (3,4))");
# clist B (30,40,50)
bt_assert(l2 = -empty-.add((3,3)).add((3,4)).add((3,5)).add((3,6)));
bt_assert(format(l2) = "(clist (3,3) (3,4) (3,5) (3,6))");
# clist A union B
clist r = l.add(l2);
bt_assert(r = -empty-.add((3,1)).add((3,2)).add((3,3)).add((3,4)).add((3,5)).add((3,6)));
bt_assert(format(r) = "(clist (3,1) (3,2) (3,3) (3,4) (3,5) (3,6))");
# clist A isect B
r = l.filter(l2);
bt_assert(r = -empty-.add((3,3)).add((3,4)));
bt_assert(format(r) = "(clist (3,3) (3,4))");
# clist A \ B
r = l.delete(l2);
bt_assert(r = -empty-.add((3,1)).add((3,2)));
bt_assert(format(r) = "(clist (3,1) (3,2))");
# clist in c set
r = l.filter([(3,1), (*,2)]);
bt_assert(r = -empty-.add((3,1)).add((3,2)));
bt_assert(format(r) = "(clist (3,1) (3,2))");
# minimim & maximum element
r = -empty-.add((2,1)).add((1,3)).add((2,2)).add((3,1)).add((2,3));
bt_assert(format(r) = "(clist (2,1) (1,3) (2,2) (3,1) (2,3))");
bt_assert(r.min = (1,3));
bt_assert(r.max = (3,1));
# iteration over clist
int x = 0;
for pair c in r do
x = x + c.asn * c.asn * c.data;
bt_assert(x = 36);
}
bt_test_suite(t_clist_new, "Testing lists of communities (new syntax)");
@ -1033,8 +1307,8 @@ bt_test_suite(t_clist, "Testing lists of communities");
*/
function t_ec()
ec cc;
{
ec cc;
cc = (rt, 12345, 200000);
bt_assert(format(cc) = "(rt, 12345, 200000)");
@ -1057,11 +1331,12 @@ bt_test_suite(t_ec, "Testing extended communities");
* -------------------------------
*/
function t_eclist()
function t_eclist_old()
eclist el;
eclist el2;
eclist r;
{
# Deprecated syntax
el = -- empty --;
el = add(el, (rt, 10, 20));
el = add(el, (ro, 10.20.30.40, 100));
@ -1144,7 +1419,99 @@ eclist r;
bt_assert(x = 3);
}
bt_test_suite(t_eclist, "Testing lists of extended communities");
bt_test_suite(t_eclist_old, "Testing lists of extended communities");
function t_eclist_new()
{
# New syntax
eclist el;
bt_assert(el = --empty--);
el.add((rt, 10, 20));
el.add((ro, 10.20.30.40, 100));
el.add((ro, 11.21.31.41.mask(16), 200));
bt_assert(--empty-- = --empty--);
bt_assert(((rt, 10, 20)) !~ --empty--);
bt_assert(format(el) = "(eclist (rt, 10, 20) (ro, 10.20.30.40, 100) (ro, 11.21.0.0, 200))");
bt_assert(el.len = 3);
el.delete((rt, 10, 20));
el.delete((rt, 10, 30));
bt_assert(el = (--empty--).add((ro, 10.20.30.40, 100)).add((ro, 11.21.0.0, 200)));
bt_assert(el.empty = --empty--);
el.add((unknown 2, ten, 1));
el.add((unknown 5, ten, 1));
el.add((rt, ten, one+one));
el.add((rt, 10, 3));
el.add((rt, 10, 4));
el.add((rt, 10, 5));
el.add((generic, 0x2000a, 3*ten));
el.delete([(rt, 10, 2..ten)]);
bt_assert(el = (--empty--).add((ro, 10.20.30.40, 100)).add((ro, 11.21.0.0, 200)).add((rt, 10, 1)).add((unknown 5, 10, 1)).add((rt, 10, 30)));
el.filter([(rt, 10, *)]);
bt_assert(el = (--empty--).add((rt, 10, 1)).add((rt, 10, 30)));
bt_assert((rt, 10, 1) ~ el);
bt_assert(el ~ [(rt, 10, ten..40)]);
bt_assert((rt, 10, 20) !~ el);
bt_assert((ro, 10.20.30.40, 100) !~ el);
bt_assert(el !~ [(rt, 10, 35..40)]);
bt_assert(el !~ [(ro, 10, *)]);
bt_assert(el !~ []);
el.add((rt, 10, 40));
eclist el2 = el.filter([(rt, 10, 20..40)] );
el2.add((rt, 10, 50));
bt_assert(el.delete([]) = el);
bt_assert(el.filter([]) = --empty--);
# eclist A (1,30,40)
bt_assert(el = --empty--.add((rt, 10, 1)).add((rt, 10, 30)).add((rt, 10, 40)));
bt_assert(format(el) = "(eclist (rt, 10, 1) (rt, 10, 30) (rt, 10, 40))");
# eclist B (30,40,50)
bt_assert(el2 = --empty--.add((rt, 10, 30)).add((rt, 10, 40)).add((rt, 10, 50)));
bt_assert(format(el2) = "(eclist (rt, 10, 30) (rt, 10, 40) (rt, 10, 50))");
# eclist A union B
eclist r = el2.add(el);
bt_assert(r = --empty--.add((rt, 10, 30)).add((rt, 10, 40)).add((rt, 10, 50)).add((rt, 10, 1)));
bt_assert(format(r) = "(eclist (rt, 10, 30) (rt, 10, 40) (rt, 10, 50) (rt, 10, 1))");
# eclist A isect B
r = el.filter(el2);
bt_assert(r = --empty--.add((rt, 10, 30)).add((rt, 10, 40)));
bt_assert(format(r) = "(eclist (rt, 10, 30) (rt, 10, 40))");
# eclist A \ B
r = el.delete(el2);
bt_assert(r = --empty--.add((rt, 10, 1)));
bt_assert(format(r) = "(eclist (rt, 10, 1))");
# eclist in ec set
r = el.filter([(rt, 10, 1), (rt, 10, 25..30), (ro, 10, 40)]);
bt_assert(r = --empty--.add((rt, 10, 1)).add((rt, 10, 30)));
bt_assert(format(r) = "(eclist (rt, 10, 1) (rt, 10, 30))");
# minimim & maximum element
r = --empty--.add((rt, 2, 1)).add((rt, 1, 3)).add((rt, 2, 2)).add((rt, 3, 1)).add((rt, 2, 3));
bt_assert(format(r) = "(eclist (rt, 2, 1) (rt, 1, 3) (rt, 2, 2) (rt, 3, 1) (rt, 2, 3))");
bt_assert(r.min = (rt, 1, 3));
bt_assert(r.max = (rt, 3, 1));
# iteration over eclist
int x = 0;
for ec c in r do
if c > (rt, 2, 0) && c < (rt, 3, 0) then
x = x + 1;
bt_assert(x = 3);
}
bt_test_suite(t_eclist_new, "Testing lists of extended communities");
@ -1188,12 +1555,12 @@ bt_test_suite(t_ec_set, "Testing sets of extended communities");
* -------------------------
*/
function mktrip(int a)
function mktrip(int a) -> lc
{
return (a, 2*a, 3*a);
}
function t_lclist()
function t_lclist_old()
lclist ll;
lclist ll2;
lclist r;
@ -1275,7 +1642,92 @@ lclist r;
bt_assert(mx = r.max);
}
bt_test_suite(t_lclist, "Testing lists of large communities");
bt_test_suite(t_lclist_old, "Testing lists of large communities");
function t_lclist_new()
{
bt_assert(---empty--- = ---empty---);
bt_assert((10, 20, 30) !~ ---empty---);
bt_assert((10, 20, 30).asn = 10);
bt_assert((10, 20, 30).data1 = 20);
bt_assert((10, 20, 30).data2 = 30);
lclist ll;
bt_assert(ll = ---empty---);
ll.add((ten, 20, 30));
ll.add((1000, 2000, 3000));
ll.add(mktrip(100000));
bt_assert(ll.empty = ---empty---);
bt_assert(format(ll) = "(lclist (10, 20, 30) (1000, 2000, 3000) (100000, 200000, 300000))");
bt_assert(ll.len = 3);
bt_assert(ll = ---empty---.add((10, 20, 30)).add((1000, 2000, 3000)).add((100000, 200000, 300000)));
bt_assert(mktrip(1000) ~ ll);
bt_assert(mktrip(100) !~ ll);
ll.empty;
ll.add((10, 10, 10));
ll.add((20, 20, 20));
ll.add((30, 30, 30));
lclist ll2;
ll2.add((20, 20, 20));
ll2.add((30, 30, 30));
ll2.add((40, 40, 40));
bt_assert(ll.delete([]) = ll);
bt_assert(ll.filter([]) = ---empty---);
# lclist A (10, 20, 30)
bt_assert(format(ll) = "(lclist (10, 10, 10) (20, 20, 20) (30, 30, 30))");
# lclist B (20, 30, 40)
bt_assert(format(ll2) = "(lclist (20, 20, 20) (30, 30, 30) (40, 40, 40))");
# lclist A union B
lclist r = ll.add(ll2);
bt_assert(r = ---empty---.add((10,10,10)).add((20,20,20)).add((30,30,30)).add((40,40,40)));
bt_assert(format(r) = "(lclist (10, 10, 10) (20, 20, 20) (30, 30, 30) (40, 40, 40))");
# lclist A isect B
r = ll.filter(ll2);
bt_assert(r = ---empty---.add((20, 20, 20)).add((30, 30, 30)));
bt_assert(format(r) = "(lclist (20, 20, 20) (30, 30, 30))");
# lclist A \ B
r = ll.delete(ll2);
bt_assert(r = ---empty---.add((10, 10, 10)));
bt_assert(format(r) = "(lclist (10, 10, 10))");
# lclist in lc set
r = ll.filter([(5..15, *, *), (20, 15..25, *)]);
bt_assert(r = ---empty---.add((10, 10, 10)).add((20, 20, 20)));
bt_assert(format(r) = "(lclist (10, 10, 10) (20, 20, 20))");
# minimim & maximum element
r = ---empty---.add((2, 3, 3)).add((1, 2, 3)).add((2, 3, 1)).add((3, 1, 2)).add((2, 1, 3));
bt_assert(format(r) = "(lclist (2, 3, 3) (1, 2, 3) (2, 3, 1) (3, 1, 2) (2, 1, 3))");
bt_assert(r.min = (1, 2, 3));
bt_assert(r.max = (3, 1, 2));
# iteration over lclist
int x = 0;
int y = 0;
lc mx = (0, 0, 0);
for lc c in r do {
int asn2 = c.asn * c.asn;
x = x + asn2 * c.data1;
y = y + asn2 * c.data2;
if c > mx then mx = c;
}
bt_assert(x = 39 && y = 49);
bt_assert(mx = r.max);
}
bt_test_suite(t_lclist_new, "Testing lists of large communities");
@ -1418,7 +1870,7 @@ bt_test_suite(t_define, "Testing defined() function");
* -------------------------
*/
function callme(int arg1; int arg2)
function callme(int arg1; int arg2) -> int
int i;
{
case arg1 {
@ -1429,12 +1881,12 @@ int i;
return 0;
}
function callmeagain(int a; int b; int c)
function callmeagain(int a; int b; int c) -> int
{
return a + b + c;
}
function fifteen()
function fifteen() -> int
{
return 15;
}
@ -1467,28 +1919,28 @@ function local_vars(int j)
bt_assert(j = 35 && k = 20 && m = 100);
}
function factorial(int x)
function factorial(int x) -> int
{
if x = 0 then return 0;
if x = 1 then return 1;
else return x * factorial(x - 1);
}
function fibonacci(int x)
function fibonacci(int x) -> int
{
if x = 0 then return 0;
if x = 1 then return 1;
else return fibonacci(x - 1) + fibonacci(x - 2);
}
function hanoi_init(int a; int b)
function hanoi_init(int a; int b) -> bgppath
{
if b = 0
then return +empty+;
else return prepend(hanoi_init(a + 1, b - 1), a);
}
function hanoi_solve(int n; bgppath h_src; bgppath h_dst; bgppath h_aux; bool x; bool y)
function hanoi_solve(int n; bgppath h_src; bgppath h_dst; bgppath h_aux; bool x; bool y) -> bgppath
{
# x -> return src or dst
# y -> print state
@ -1798,6 +2250,15 @@ filter vpn_filter
bgp_ext_community.add((ro, 135, 999));
bgp_large_community.add((6464156, 89646354, 8675643));
mypath.prepend(65533);
mylclist.add((1234, 5678, 90123));
bgppath locpath;
lclist loclclist;
locpath.prepend(65533);
loclclist.add((1234, 5678, 90123));
accept;
}

3
filter/test.conf.overlay Normal file
View File

@ -0,0 +1,3 @@
attribute int peek_a_boo;
include "test.conf";

View File

@ -28,12 +28,6 @@ struct f_prefix_node {
struct f_prefix prefix;
};
static u32
xrandom(u32 max)
{
return (bt_random() % max);
}
static inline uint
get_exp_random(void)
{
@ -95,27 +89,10 @@ is_prefix_included(list *prefixes, const net_addr *needle)
return 0; /* FAIL */
}
static void
get_random_net(net_addr *net, int v6)
{
if (!v6)
{
uint pxlen = xrandom(24)+8;
ip4_addr ip4 = ip4_from_u32((u32) bt_random());
net_fill_ip4(net, ip4_and(ip4, ip4_mkmask(pxlen)), pxlen);
}
else
{
uint pxlen = xrandom(120)+8;
ip6_addr ip6 = ip6_build(bt_random(), bt_random(), bt_random(), bt_random());
net_fill_ip6(net, ip6_and(ip6, ip6_mkmask(pxlen)), pxlen);
}
}
static void
get_random_prefix(struct f_prefix *px, int v6, int tight)
{
get_random_net(&px->net, v6);
bt_random_net(&px->net, !v6 ? NET_IP4 : NET_IP6);
if (tight)
{
@ -379,7 +356,7 @@ select_random_prefix_subset(list *src[], net_addr dst[], int sn, int dn)
struct f_prefix_node *px;
WALK_LIST(px, *src[i])
{
if (xrandom(rnd) != 0)
if (bt_random_n(rnd) != 0)
continue;
net_copy(&dst[n], &px->prefix.net);
@ -395,7 +372,7 @@ done:
/* Shuffle networks */
for (int i = 0; i < dn; i++)
{
int j = xrandom(dn);
int j = bt_random_n(dn);
if (i == j)
continue;
@ -441,7 +418,7 @@ t_match_random_net(void)
for (int i = 0; i < PREFIX_TESTS_NUM; i++)
{
net_addr net;
get_random_net(&net, v6);
bt_random_net(&net, !v6 ? NET_IP4 : NET_IP6);
test_match_net(prefixes, trie, &net);
}
@ -795,7 +772,7 @@ t_trie_walk_to_root(void)
for (i = 0; i < (PREFIX_TESTS_NUM / 10); i++)
{
net_addr from;
get_random_net(&from, v6);
bt_random_net(&from, !v6 ? NET_IP4 : NET_IP6);
net_addr found[129];
int found_num = find_covering_nets(pxset, num, &from, found);

View File

@ -8,7 +8,7 @@
*/
#include "nest/bird.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "lib/attrs.h"
#include "lib/resource.h"
#include "lib/unaligned.h"
@ -669,6 +669,29 @@ as_path_filter(struct linpool *pool, const struct adata *path, const struct f_va
return res;
}
int
as_path_compare(const struct adata *path1, const struct adata *path2)
{
uint pos1 = 0;
uint pos2 = 0;
uint val1 = 0;
uint val2 = 0;
while (1)
{
int res1 = as_path_walk(path1, &pos1, &val1);
int res2 = as_path_walk(path2, &pos2, &val2);
if (res1 == 0 && res2 == 0)
return 0;
if (val1 == val2)
continue;
return val1 < val2 ? -1 : 1;
}
}
int
as_path_walk(const struct adata *path, uint *pos, uint *val)
{

View File

@ -9,7 +9,7 @@
#include "test/birdtest.h"
#include "test/bt-utils.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "lib/attrs.h"
#include "lib/resource.h"
#include "filter/data.h"

View File

@ -10,7 +10,7 @@
#include <stdlib.h>
#include "nest/bird.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "lib/attrs.h"
#include "lib/resource.h"
#include "lib/string.h"

View File

@ -10,7 +10,7 @@
#include "test/bt-utils.h"
#include "lib/net.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "lib/attrs.h"
#include "lib/resource.h"

View File

@ -83,6 +83,7 @@ u32 as_path_get_last_nonaggregated(const struct adata *path);
int as_path_contains(const struct adata *path, u32 as, int min);
int as_path_match_set(const struct adata *path, const struct f_tree *set);
const struct adata *as_path_filter(struct linpool *pool, const struct adata *path, const struct f_val *set, int pos);
int as_path_compare(const struct adata *path1, const struct adata *path2);
int as_path_walk(const struct adata *path, uint *pos, uint *val);
static inline struct adata *as_path_prepend(struct linpool *pool, const struct adata *path, u32 as)

View File

@ -239,10 +239,36 @@ asm(
);
#endif
/* Pseudorandom numbers */
u32 random_u32(void);
void random_init(void);
void random_bytes(void *buf, size_t size);
/* Hashing */
/* Constant parameter for non-parametrized hashes */
#define HASH_PARAM 2902958171u
/* Precomputed powers of HASH_PARAM */
#define HASH_PARAM1 ((u64) HASH_PARAM)
#define HASH_PARAM2 (HASH_PARAM1 * HASH_PARAM)
#define HASH_PARAM3 (HASH_PARAM2 * HASH_PARAM)
#define HASH_PARAM4 (HASH_PARAM3 * HASH_PARAM)
/* Reduce intermediate 64-bit value to final 32-bit value */
static inline u32 hash_value(u64 a)
{ return ((u32) a) ^ ((u32) (a >> 32)); }
static inline u64 u32_hash0(u32 v, u32 p, u64 acc)
{ return (acc + v) * p; }
static inline u64 u64_hash0(u64 v, u32 p, u64 acc)
{ return u32_hash0(v >> 32, p, u32_hash0(v, p, acc)); }
static inline u32 u64_hash(u64 v)
{ return hash_value(u64_hash0(v, HASH_PARAM, 0)); }
#endif

View File

@ -197,3 +197,217 @@ hmap_check(struct hmap *b)
}
}
}
/*
* Indirect bitmap for MPLS labels (20 bit range)
*/
void
lmap_init(struct lmap *b, pool *p)
{
b->slab = sl_new(p, 128);
b->size = 8;
b->data = mb_allocz(p, b->size * sizeof(u32 *));
b->root = sl_allocz(b->slab);
}
static void
lmap_grow(struct lmap *b, uint need)
{
uint old_size = b->size;
while (b->size < need)
b->size *= 2;
b->data = mb_realloc(b->data, b->size * sizeof(u32 *));
memset(b->data + old_size, 0, (b->size - old_size) * sizeof(u32 *));
}
void
lmap_free(struct lmap *b)
{
rfree(b->slab);
mb_free(b->data);
memset(b, 0, sizeof(struct lmap));
}
static inline int
b1024_and(u32 *p)
{
for (int i = 0; i < 32; i++)
if (~p[i])
return 0;
return 1;
}
static inline int
b1024_or(u32 *p)
{
for (int i = 0; i < 32; i++)
if (p[i])
return 1;
return 0;
}
int
lmap_test(struct lmap *b, uint n)
{
uint n0 = n >> 10;
uint n1 = n & 0x3ff;
return (n0 < b->size) && b->data[n0] && BIT32_TEST(b->data[n0], n1);
}
void
lmap_set(struct lmap *b, uint n)
{
uint n0 = n >> 10;
uint n1 = n & 0x3ff;
if (n0 >= b->size)
lmap_grow(b, n0 + 1);
if (! b->data[n0])
b->data[n0] = sl_allocz(b->slab);
BIT32_SET(b->data[n0], n1);
if (b1024_and(b->data[n0]))
BIT32_SET(b->root, n0);
}
void
lmap_clear(struct lmap *b, uint n)
{
uint n0 = n >> 10;
uint n1 = n & 0x3ff;
if (n0 >= b->size)
return;
if (! b->data[n0])
return;
BIT32_CLR(b->data[n0], n1);
BIT32_CLR(b->root, n0);
if (!b1024_or(b->data[n0]))
{
sl_free(b->data[n0]);
b->data[n0] = NULL;
}
}
static inline int
b1024_first_zero(u32 *p)
{
for (int i = 0; i < 32; i++)
if (~p[i])
return 32*i + u32_ctz(~p[i]);
return 1024;
}
uint
lmap_first_zero(struct lmap *b)
{
uint n0 = b1024_first_zero(b->root);
uint n1 = ((n0 < b->size) && b->data[n0]) ?
b1024_first_zero(b->data[n0]) : 0;
return (n0 << 10) + n1;
}
static uint
b1024_first_zero_in_range(u32 *p, uint lo, uint hi)
{
uint lo0 = lo >> 5;
uint lo1 = lo & 0x1f;
uint hi0 = hi >> 5;
uint hi1 = hi & 0x1f;
u32 mask = (1 << lo1) - 1;
u32 val;
for (uint i = lo0; i < hi0; i++)
{
val = p[i] | mask;
mask = 0;
if (~val)
return 32*i + u32_ctz(~val);
}
if (hi1)
{
mask |= ~((1u << hi1) - 1);
val = p[hi0] | mask;
if (~val)
return 32*hi0 + u32_ctz(~val);
}
return hi;
}
uint
lmap_first_zero_in_range(struct lmap *b, uint lo, uint hi)
{
uint lo0 = lo >> 10;
uint lo1 = lo & 0x3ff;
uint hi0 = hi >> 10;
uint hi1 = hi & 0x3ff;
if (lo1)
{
uint max = (lo0 == hi0) ? hi1 : 1024;
uint n0 = lo0;
uint n1 = ((n0 < b->size) && b->data[n0]) ?
b1024_first_zero_in_range(b->data[n0], lo1, max) : lo1;
if (n1 < 1024)
return (n0 << 10) + n1;
lo0++;
lo1 = 0;
}
if (lo0 < hi0)
{
uint n0 = b1024_first_zero_in_range(b->root, lo0, hi0);
if (n0 < hi0)
{
uint n1 = ((n0 < b->size) && b->data[n0]) ?
b1024_first_zero(b->data[n0]) : 0;
return (n0 << 10) + n1;
}
}
if (hi1)
{
uint n0 = hi0;
uint n1 = ((n0 < b->size) && b->data[n0]) ?
b1024_first_zero_in_range(b->data[n0], 0, hi1) : 0;
return (n0 << 10) + n1;
}
return hi;
}
void
lmap_check(struct lmap *b)
{
for (int i = 0; i < (int) b->size; i++)
{
int x = b->data[i] && b1024_and(b->data[i]);
int y = !!BIT32_TEST(b->root, i);
if (x != y)
bug("Inconsistent data on %d (%d vs %d)", i, x, y);
}
}

View File

@ -60,4 +60,25 @@ void hmap_clear(struct hmap *b, uint n);
u32 hmap_first_zero(struct hmap *b);
void hmap_check(struct hmap *b);
struct lmap
{
slab *slab;
uint size;
u32 **data;
u32 *root;
};
static inline uint lmap_max(struct lmap *b)
{ return b->size << 10; }
void lmap_init(struct lmap *b, pool *p);
void lmap_free(struct lmap *b);
int lmap_test(struct lmap *b, uint n);
void lmap_set(struct lmap *b, uint n);
void lmap_clear(struct lmap *b, uint n);
uint lmap_first_zero(struct lmap *b);
uint lmap_first_zero_in_range(struct lmap *b, uint lo, uint hi);
void lmap_check(struct lmap *b);
#endif

View File

@ -170,6 +170,70 @@ t_hmap_set_clear_fill(void)
return 1;
}
static int
t_lmap_set_clear_fill(void)
{
struct lmap b;
lmap_init(&b, &root_pool);
char expected[MAX_NUM] = {};
uint i, j, n;
for (i = 0; i < STEP_NUM; i++)
{
uint last = 0;
uint lo = bt_random() % (1 << 19);
uint hi = lo + 2 * STEP_SET;
uint step_set = bt_random() % STEP_SET;
uint step_clr = bt_random() % STEP_CLR;
for (j = 0; j < step_set; j++)
{
n = lmap_first_zero_in_range(&b, lo, hi);
bt_assert(n >= lo);
bt_assert(n <= hi);
for (last = lo; last < n; last++)
bt_assert(expected[last]);
if (n >= hi)
break;
bt_assert(!expected[n]);
lmap_set(&b, n);
expected[n] = 1;
}
for (j = 0; j < step_clr; j++)
{
n = lo + bt_random() % (step_set + 1);
if (!expected[n])
continue;
lmap_clear(&b, n);
expected[n] = 0;
}
}
uint cnt = 0;
for (i = 0; i < MAX_NUM; i++)
{
if (lmap_test(&b, i) != expected[i])
bt_abort_msg("Bitmap mismatch on %d (should be %d %d)", i, lmap_test(&b, i), expected[i]);
if (expected[i])
cnt++;
}
// bt_log("Total %u", cnt);
lmap_check(&b);
return 1;
}
int
main(int argc, char *argv[])
{
@ -178,6 +242,7 @@ main(int argc, char *argv[])
bt_test_suite(t_bmap_set_clear_random, "BMap - random sequence of sets / clears");
bt_test_suite(t_hmap_set_clear_random, "HMap - random sequence of sets / clears");
bt_test_suite(t_hmap_set_clear_fill, "HMap - linear sets and random clears");
bt_test_suite(t_lmap_set_clear_fill, "LMap - linear sets and random clears");
return bt_exit_value();
}

View File

@ -15,7 +15,7 @@
#include "nest/locks.h"
#include "sysdep/unix/unix.h"
#include "nest/iface.h"
#include "nest/rt.h"
#include "nest/route.h"
#define MAX_NUM 4

View File

@ -235,6 +235,14 @@ mem_hash_mix(u64 *h, const void *p, uint s)
*h = *h * multiplier + pp[i];
}
static inline void
mem_hash_mix_str(u64 *h, const char *s)
{
const u64 multiplier = 0xb38bc09a61202731ULL;
while (s)
*h = *h * multiplier + *s++;
}
static inline void
mem_hash_mix_num(u64 *h, u64 val)
{

View File

@ -194,14 +194,37 @@ static inline int ipa_nonzero2(ip_addr a)
* Hash and compare functions
*/
static inline u64 ip4_hash0(ip4_addr a, u32 p, u64 acc)
{ return (acc + _I(a)) * p; }
static inline u32 ip4_hash(ip4_addr a)
{ return u32_hash(_I(a)); }
{
// return hash_value(ip4_hash0(a, HASH_PARAM, 0));
/* For some reason, the old hash works slightly better */
return u32_hash(_I(a));
}
static inline u64 ip6_hash0(ip6_addr a, u32 p, u64 acc)
{
acc += _I0(a); acc *= p;
acc += _I1(a); acc *= p;
acc += _I2(a); acc *= p;
acc += _I3(a); acc *= p;
return acc;
}
static inline u32 ip6_hash(ip6_addr a)
{
/* Returns a 32-bit hash key, although low-order bits are not mixed */
u32 x = _I0(a) ^ _I1(a) ^ _I2(a) ^ _I3(a);
return x ^ (x << 16) ^ (x << 24);
// return hash_value(ip6_hash0(a, HASH_PARAM, 0));
/* Just use the expanded form */
u64 acc =
_I0(a) * HASH_PARAM4 +
_I1(a) * HASH_PARAM3 +
_I2(a) * HASH_PARAM2 +
_I3(a) * HASH_PARAM1;
return hash_value(acc);
}
static inline int ip4_compare(ip4_addr a, ip4_addr b)

View File

@ -479,39 +479,47 @@ static inline void net_copy_mpls(net_addr_mpls *dst, const net_addr_mpls *src)
{ memcpy(dst, src, sizeof(net_addr_mpls)); }
/* XXXX */
static inline u32 u64_hash(u64 a)
{ return u32_hash(a); }
static inline u32 px4_hash(ip4_addr prefix, u32 pxlen)
{ return ip4_hash(prefix) ^ (pxlen << 26); }
static inline u32 px6_hash(ip6_addr prefix, u32 pxlen)
{ return ip6_hash(prefix) ^ (pxlen << 26); }
static inline u32 net_hash_ip4(const net_addr_ip4 *n)
{ return ip4_hash(n->prefix) ^ ((u32) n->pxlen << 26); }
{ return px4_hash(n->prefix, n->pxlen); }
static inline u32 net_hash_ip6(const net_addr_ip6 *n)
{ return ip6_hash(n->prefix) ^ ((u32) n->pxlen << 26); }
{ return px6_hash(n->prefix, n->pxlen); }
static inline u32 net_hash_vpn4(const net_addr_vpn4 *n)
{ return ip4_hash(n->prefix) ^ ((u32) n->pxlen << 26) ^ u64_hash(n->rd); }
{
u64 acc = ip4_hash0(n->prefix, HASH_PARAM, 0) ^ (n->pxlen << 26);
return hash_value(u64_hash0(n->rd, HASH_PARAM, acc));
}
static inline u32 net_hash_vpn6(const net_addr_vpn6 *n)
{ return ip6_hash(n->prefix) ^ ((u32) n->pxlen << 26) ^ u64_hash(n->rd); }
{
u64 acc = ip6_hash0(n->prefix, HASH_PARAM, 0) ^ (n->pxlen << 26);
return hash_value(u64_hash0(n->rd, HASH_PARAM, acc));
}
static inline u32 net_hash_roa4(const net_addr_roa4 *n)
{ return ip4_hash(n->prefix) ^ ((u32) n->pxlen << 26); }
{ return px4_hash(n->prefix, n->pxlen); }
static inline u32 net_hash_roa6(const net_addr_roa6 *n)
{ return ip6_hash(n->prefix) ^ ((u32) n->pxlen << 26); }
{ return px6_hash(n->prefix, n->pxlen); }
static inline u32 net_hash_flow4(const net_addr_flow4 *n)
{ return ip4_hash(n->prefix) ^ ((u32) n->pxlen << 26); }
{ return px4_hash(n->prefix, n->pxlen); }
static inline u32 net_hash_flow6(const net_addr_flow6 *n)
{ return ip6_hash(n->prefix) ^ ((u32) n->pxlen << 26); }
{ return px6_hash(n->prefix, n->pxlen); }
static inline u32 net_hash_ip6_sadr(const net_addr_ip6_sadr *n)
{ return net_hash_ip6((net_addr_ip6 *) n); }
{ return px6_hash(n->dst_prefix, n->dst_pxlen); }
static inline u32 net_hash_mpls(const net_addr_mpls *n)
{ return n->label; }
{ return u32_hash(n->label); }
u32 net_hash(const net_addr *a);

View File

@ -336,6 +336,7 @@ resource_init(void)
rcu_init();
resource_sys_init();
root_pool.r.class = &pool_class;
rp_init(&root_pool, the_bird_domain.the_bird, "Root");
tmp_init(&root_pool, the_bird_domain.the_bird);
}

View File

@ -60,10 +60,12 @@ static inline rte rte_init_from(const rte *r)
};
}
int rte_same(const rte *, const rte *);
struct rte_src {
struct rte_src *next; /* Hash chain */
struct rte_owner *owner; /* Route source owner */
u32 private_id; /* Private ID, assigned by the protocol */
u64 private_id; /* Private ID, assigned by the protocol */
u32 global_id; /* Globally unique ID of the source */
_Atomic u64 uc; /* Use count */
};
@ -171,6 +173,8 @@ static inline void rt_unlock_source(struct rte_src *src)
void rt_init_sources(struct rte_owner *, const char *name, event_list *list);
void rt_destroy_sources(struct rte_owner *, event *);
void rt_dump_sources(struct rte_owner *);
/*
* Route Attributes
*
@ -221,7 +225,8 @@ struct nexthop_adata {
#define RTS_BABEL 13 /* Babel route */
#define RTS_RPKI 14 /* Route Origin Authorization */
#define RTS_PERF 15 /* Perf checker */
#define RTS_MAX 16
#define RTS_AGGREGATED 16 /* Aggregated route */
#define RTS_MAX 17
#define RTD_NONE 0 /* Undefined next hop */
#define RTD_UNICAST 1 /* A standard next hop */
@ -305,6 +310,7 @@ struct ea_class_ref {
void ea_register_init(struct ea_class *);
struct ea_class_ref *ea_register_alloc(pool *, struct ea_class);
struct ea_class_ref *ea_ref_class(pool *, struct ea_class *); /* Reference for an attribute alias */
#define EA_REGISTER_ALL_HELPER(x) ea_register_init(x);
#define EA_REGISTER_ALL(...) MACRO_FOREACH(EA_REGISTER_ALL_HELPER, __VA_ARGS__)

View File

@ -33,6 +33,9 @@ u64 bstrtoul10(const char *str, char **end);
u64 bstrtoul16(const char *str, char **end);
byte bstrtobyte16(const char *str);
int bstrhextobin(const char *s, byte *b);
int bstrbintohex(const byte *b, size_t len, char *buf, size_t size, char delim);
int patmatch(const byte *pat, const byte *str);
static inline char *xbasename(const char *str)

View File

@ -25,7 +25,7 @@ bstrtoul10(const char *str, char **end)
errno = ERANGE;
return UINT64_MAX;
}
out *= 10;
out += (**end) - '0';
}
@ -60,29 +60,91 @@ bstrtoul16(const char *str, char **end)
return UINT64_MAX;
}
byte
bstrtobyte16(const char *str)
static int
fromxdigit(char c)
{
byte out = 0;
for (int i=0; i<2; i++) {
switch (str[i]) {
case '0' ... '9':
out *= 16;
out += str[i] - '0';
break;
case 'a' ... 'f':
out *= 16;
out += str[i] + 10 - 'a';
break;
case 'A' ... 'F':
out *= 16;
out += str[i] + 10 - 'A';
break;
default:
errno = ERANGE;
switch (c)
{
case '0' ... '9':
return c - '0';
case 'a' ... 'f':
return c + 10 - 'a';
case 'A' ... 'F':
return c + 10 - 'A';
default:
return -1;
}
}
int
bstrhextobin(const char *s, byte *b)
{
int len = 0;
int hi = 0;
for (; *s; s++)
{
int v = fromxdigit(*s);
if (v < 0)
{
if (strchr(" :-", *s) && !hi)
continue;
else
return -1;
}
if (len == INT32_MAX)
return -1;
if (b)
{
if (!hi)
b[len] = (v << 4);
else
b[len] |= v;
}
len += hi;
hi = !hi;
}
return out;
return !hi ? len : -1;
}
static char
toxdigit(uint b)
{
if (b < 10)
return ('0' + b);
else if (b < 16)
return ('a' + b - 10);
else
return 0;
}
int
bstrbintohex(const byte *b, size_t len, char *buf, size_t size, char delim)
{
ASSERT(size >= 6);
char *bound = buf + size - 3;
size_t i;
for (i = 0; i < len; i++)
{
if (buf > bound)
{
strcpy(buf - 4, "...");
return -1;
}
uint x = b[i];
buf[0] = toxdigit(x >> 4);
buf[1] = toxdigit(x & 0xF);
buf[2] = delim;
buf += 3;
}
buf[i ? -1 : 0] = 0;
return 0;
}

View File

@ -55,6 +55,9 @@ static inline timer *timers_first(struct timeloop *loop)
#define current_time() atomic_load_explicit(&last_time, memory_order_acquire)
#define current_real_time() atomic_load_explicit(&real_time, memory_order_acquire)
/* In sysdep code */
btime current_time_now(void);
//#define now (current_time() TO_S)
//#define now_real (current_real_time() TO_S)
extern btime boot_time;

View File

@ -37,10 +37,16 @@ union bval_long {
ip_addr ip;
const net_addr *net;
const char *s;
const struct adata *bs;
const struct f_tree *t;
const struct f_trie *ti;
const struct f_path_mask *path_mask;
struct f_path_mask_item pmi;
struct rte *rte;
struct rte_block {
struct rte **rte;
uint len;
} rte_block;
};
@ -48,12 +54,15 @@ union bval_long {
enum btype {
/* Nothing. Simply nothing. */
T_VOID = 0,
T_NONE = 0xff,
/* Something but inaccessible. */
T_OPAQUE = 0x02, /* Opaque byte string (not filterable) */
T_IFACE = 0x0c, /* Pointer to an interface (inside adata) */
T_NEXTHOP_LIST = 0x2c, /* The whole nexthop block */
T_HOSTENTRY = 0x2e, /* Hostentry with possible MPLS labels */
T_ROUTES_BLOCK = 0x68, /* Block of route pointers */
T_ROUTE = 0x6a, /* One route pointer */
T_NEXTHOP_LIST = 0x6c, /* The whole nexthop block */
T_HOSTENTRY = 0x6e, /* Hostentry with possible MPLS labels */
/* Types shared with eattrs */
T_INT = 0x01, /* 32-bit unsigned integer number */
@ -99,6 +108,7 @@ enum btype {
T_LC = 0xc0, /* Large community value, lcomm */
T_RD = 0xc4, /* Route distinguisher for VPN addresses */
T_PATH_MASK_ITEM = 0xc8, /* Path mask item for path mask constructors */
T_BYTESTRING = 0xcc,
T_SET = 0x80,
T_PREFIX_SET = 0x84,

View File

@ -13,6 +13,6 @@ $(o)proto-build.c: Makefile $(lastword $(MAKEFILE_LIST)) $(objdir)/.dir-stamp
prepare: $(o)proto-build.c
tests_src :=
tests_src := rt-fib_test.c
tests_targets := $(tests_targets) $(tests-target-files)
tests_objs := $(tests_objs) $(src-o-files)

View File

@ -8,7 +8,7 @@
#include "nest/bird.h"
#include "nest/protocol.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/cli.h"
#include "conf/conf.h"
#include "nest/cmds.h"
@ -54,9 +54,6 @@ cmd_show_symbols(struct sym_show_data *sd)
for (const struct sym_scope *scope = config->root_scope; scope; scope = scope->next)
HASH_WALK(scope->hash, next, sym)
{
if (!sym->scope->active)
continue;
if (sd->type && (sym->class != sd->type))
continue;

View File

@ -204,7 +204,7 @@ rtrid:
idval:
NUM { $$ = $1; }
| '(' term ')' { $$ = f_eval_int(f_linearize($2, 1)); }
| '(' term ')' { $$ = cf_eval_int($2); }
| IP4 { $$ = ip4_to_u32($1); }
| CF_SYM_KNOWN {
if ($1->class == (SYM_CONSTANT | T_INT) || $1->class == (SYM_CONSTANT | T_QUAD))
@ -299,19 +299,19 @@ proto_start:
proto_name:
/* EMPTY */ {
struct symbol *s = cf_default_name(this_proto->protocol->template, &this_proto->protocol->name_counter);
struct symbol *s = cf_default_name(new_config, this_proto->protocol->template, &this_proto->protocol->name_counter);
s->class = this_proto->class;
s->proto = this_proto;
this_proto->name = s->name;
}
| symbol {
cf_define_symbol($1, this_proto->class, proto, this_proto);
cf_define_symbol(new_config, $1, this_proto->class, proto, this_proto);
this_proto->name = $1->name;
}
| FROM CF_SYM_KNOWN {
if (($2->class != SYM_TEMPLATE) && ($2->class != SYM_PROTO)) cf_error("Template or protocol name expected");
struct symbol *s = cf_default_name(this_proto->protocol->template, &this_proto->protocol->name_counter);
struct symbol *s = cf_default_name(new_config, this_proto->protocol->template, &this_proto->protocol->name_counter);
s->class = this_proto->class;
s->proto = this_proto;
this_proto->name = s->name;
@ -321,7 +321,7 @@ proto_name:
| symbol FROM CF_SYM_KNOWN {
if (($3->class != SYM_TEMPLATE) && ($3->class != SYM_PROTO)) cf_error("Template or protocol name expected");
cf_define_symbol($1, this_proto->class, proto, this_proto);
cf_define_symbol(new_config, $1, this_proto->class, proto, this_proto);
this_proto->name = $1->name;
proto_copy_config(this_proto, $3->proto);
@ -608,10 +608,15 @@ password_item:
pass_key: PASSWORD | KEY;
password_item_begin:
pass_key text { init_password_list(); init_password($2, strlen($2), password_id++); }
| pass_key BYTESTRING { init_password_list(); init_password($2->data, $2->length, password_id++); }
;
password_item_begin: pass_key bytestring_text
{
init_password_list();
if ($2.type == T_BYTESTRING)
init_password($2.val.bs->data, $2.val.bs->length, password_id++);
else if ($2.type == T_STRING)
init_password($2.val.s, strlen($2.val.s), password_id++);
else bug("Bad bytestring_text");
};
password_item_params:
/* empty */ { }
@ -726,7 +731,7 @@ r_args:
$$->addr = $3;
$$->addr_mode = TE_ADDR_IN;
}
| r_args TABLE symbol_known {
| r_args TABLE CF_SYM_KNOWN {
cf_assert_symbol($3, SYM_TABLE);
if (!$3->table) cf_error("Table %s not configured", $3->name);
$$ = $1;
@ -773,7 +778,7 @@ r_args:
$$ = $1;
$$->filtered = 1;
}
| r_args export_mode symbol_known {
| r_args export_mode CF_SYM_KNOWN {
cf_assert_symbol($3, SYM_PROTO);
struct proto_config *c = (struct proto_config *) $3->proto;
$$ = $1;
@ -790,7 +795,7 @@ r_args:
$$->export_channel = $3;
$$->tables_defined_by = RSD_TDB_INDIRECT;
}
| r_args PROTOCOL symbol_known {
| r_args PROTOCOL CF_SYM_KNOWN {
cf_assert_symbol($3, SYM_PROTO);
struct proto_config *c = (struct proto_config *) $3->proto;
$$ = $1;
@ -902,7 +907,7 @@ sym_args:
| sym_args FILTER { $$ = $1; $$->type = SYM_FILTER; }
| sym_args PROTOCOL { $$ = $1; $$->type = SYM_PROTO; }
| sym_args TEMPLATE { $$ = $1; $$->type = SYM_TEMPLATE; }
| sym_args symbol { $$ = $1; $$->sym = $2; }
| sym_args CF_SYM_KNOWN { $$ = $1; $$->sym = $2; }
;

View File

@ -288,7 +288,7 @@ ifa_send_notify(struct iface_subscription *s, unsigned c, struct ifa *a)
if (s->ifa_notify &&
(p->proto_state != PS_DOWN) &&
(!p->vrf || p->vrf == a->iface->master))
(!p->vrf || if_in_vrf(a->iface, p->vrf)))
{
if (p->debug & D_IFACES)
log(L_TRACE "%s < address %N on interface %s %s",
@ -330,7 +330,7 @@ if_send_notify(struct iface_subscription *s, unsigned c, struct iface *i)
if (s->if_notify &&
(p->proto_state != PS_DOWN) &&
(!p->vrf || p->vrf == i->master))
(!p->vrf || if_in_vrf(i, p->vrf)))
{
if (p->debug & D_IFACES)
log(L_TRACE "%s < interface %s %s", p->name, i->name,

View File

@ -58,6 +58,7 @@ struct iface {
#define IF_IGNORE 0x40 /* Not to be used by routing protocols (loopbacks etc.) */
#define IF_ADMIN_UP 0x80 /* Administrative up (e.g. IFF_UP in Linux) */
#define IF_LINK_UP 0x100 /* Link available (e.g. IFF_LOWER_UP in Linux) */
#define IF_VRF 0x200 /* Iface is VRF master */
#define IA_PRIMARY 0x10000 /* This address is primary */
#define IA_SECONDARY 0x20000 /* This address has been reported as secondary by the kernel */
@ -124,6 +125,9 @@ struct iface *if_find_by_name(const char *);
struct iface *if_get_by_name(const char *);
void if_recalc_all_preferred_addresses(void);
static inline int if_in_vrf(struct iface *i, struct iface *vrf)
{ return (i->flags & IF_VRF) ? (i == vrf) : (i->master == vrf); }
struct iface *if_walk_first(void);
struct iface *if_walk_next(struct iface *);
void if_walk_done(void);

View File

@ -166,7 +166,7 @@ if_connected_any(ip_addr a, struct iface *vrf, struct iface **iface, struct ifa
/* Prefer SCOPE_HOST or longer prefix */
WALK_LIST(i, global_iface_list)
if ((!vrf || vrf == i->master) && ((s = if_connected(a, i, &b, flags)) >= 0))
if ((!vrf || if_in_vrf(i,vrf)) && ((s = if_connected(a, i, &b, flags)) >= 0))
if (scope_better(s, scope) || (scope_remote(s, scope) && ifa_better(b, *addr)))
{
*iface = i;
@ -432,7 +432,7 @@ neigh_update(neighbor *n, struct iface *iface)
return;
/* VRF-bound neighbors ignore changes in other VRFs */
if (p->vrf && (p->vrf != iface->master))
if (p->vrf && !if_in_vrf(iface, p->vrf))
return;
scope = if_connected(n->addr, iface, &ifa, n->flags);

View File

@ -16,7 +16,7 @@
#include "lib/timer.h"
#include "lib/string.h"
#include "conf/conf.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/iface.h"
#include "nest/cli.h"
#include "filter/filter.h"
@ -184,7 +184,6 @@ proto_log_state_change(struct proto *p)
p->last_state_name_announced = NULL;
}
struct channel_config *
proto_cf_find_channel(struct proto_config *pc, uint net_type)
{
@ -687,10 +686,12 @@ channel_start_export(struct channel *c)
ASSERT(c->channel_state == CS_UP);
pool *p = rp_newf(c->proto->pool, c->proto->pool->domain, "Channel %s.%s export", c->proto->name, c->name);
c->out_req = (struct rt_export_request) {
.name = mb_sprintf(c->proto->pool, "%s.%s", c->proto->name, c->name),
.name = mb_sprintf(p, "%s.%s", c->proto->name, c->name),
.list = proto_work_list(c->proto),
.pool = c->proto->pool,
.pool = p,
.feed_block_size = c->feed_block_size,
.prefilter = {
.mode = c->out_subprefix ? TE_ADDR_IN : TE_ADDR_NONE,
@ -702,8 +703,8 @@ channel_start_export(struct channel *c)
.mark_seen = channel_rpe_mark_seen_export,
};
bmap_init(&c->export_map, c->proto->pool, 16);
bmap_init(&c->export_reject_map, c->proto->pool, 16);
bmap_init(&c->export_map, p, 16);
bmap_init(&c->export_reject_map, p, 16);
channel_reset_limit(c, &c->out_limit, PLD_OUT);
@ -728,7 +729,7 @@ channel_start_export(struct channel *c)
}
c->refeed_req = c->out_req;
c->refeed_req.name = mb_sprintf(c->proto->pool, "%s.%s.refeed", c->proto->name, c->name);
c->refeed_req.name = mb_sprintf(p, "%s.%s.refeed", c->proto->name, c->name);
c->refeed_req.dump_req = channel_dump_refeed_req;
c->refeed_req.log_state_change = channel_refeed_log_state_change;
c->refeed_req.mark_seen = channel_rpe_mark_seen_refeed;
@ -795,12 +796,12 @@ channel_export_stopped(struct rt_export_request *req)
return;
}
mb_free(c->out_req.name);
c->out_req.name = NULL;
bmap_free(&c->export_map);
bmap_free(&c->export_reject_map);
c->out_req.name = NULL;
rfree(c->out_req.pool);
channel_check_stopped(c);
}
@ -1545,10 +1546,10 @@ proto_event(void *ptr)
p->do_stop = 0;
}
if (proto_is_done(p) && p->pool_fragile) /* perusing pool_fragile to do this once only */
if (proto_is_done(p) && p->pool_inloop) /* perusing pool_inloop to do this once only */
{
rp_free(p->pool_fragile);
p->pool_fragile = NULL;
rp_free(p->pool_inloop);
p->pool_inloop = NULL;
if (p->loop != &main_birdloop)
birdloop_stop_self(p->loop, proto_loop_stopped, p);
else
@ -1630,7 +1631,8 @@ proto_start(struct proto *p)
PROTO_LOCKED_FROM_MAIN(p)
{
p->pool_fragile = rp_newf(p->pool, birdloop_domain(p->loop), "Protocol %s fragile objects", p->cf->name);
p->pool_inloop = rp_newf(p->pool, birdloop_domain(p->loop), "Protocol %s early cleanup objects", p->cf->name);
p->pool_up = rp_newf(p->pool, birdloop_domain(p->loop), "Protocol %s stop-free objects", p->cf->name);
proto_notify_state(p, (p->proto->start ? p->proto->start(p) : PS_UP));
}
}
@ -1893,8 +1895,8 @@ protos_do_commit(struct config *new, struct config *old, int force_reconfig, int
/* This is hack, we would like to share config, but we need to copy it now */
new_config = new;
cfg_mem = new->mem;
conf_this_scope = new->root_scope;
sym = cf_get_symbol(oc->name);
new->current_scope = new->root_scope;
sym = cf_get_symbol(new, oc->name);
proto_clone_config(sym, parsym->proto);
new_config = NULL;
cfg_mem = NULL;
@ -2273,6 +2275,9 @@ protos_dump_all(void)
c->out_req.hook ? rt_export_state_name(rt_export_get_state(c->out_req.hook)) : "-");
}
debug("\tSOURCES\n");
rt_dump_sources(&p->sources);
if (p->proto->dump && (p->proto_state != PS_DOWN))
p->proto->dump(p);
}
@ -2563,6 +2568,9 @@ proto_do_stop(struct proto *p)
p->main_source = NULL;
}
rp_free(p->pool_up);
p->pool_up = NULL;
proto_stop_channels(p);
rt_destroy_sources(&p->sources, p->event);

View File

@ -14,7 +14,7 @@
#include "lib/event.h"
#include "nest/iface.h"
#include "lib/settle.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/limit.h"
#include "conf/conf.h"
#include "filter/data.h"
@ -93,8 +93,8 @@ void protos_dump_all(void);
extern struct protocol
proto_device, proto_radv, proto_rip, proto_static, proto_mrt,
proto_ospf, proto_perf,
proto_pipe, proto_bgp, proto_bfd, proto_babel, proto_rpki;
proto_ospf, proto_perf, proto_aggregator,
proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki;
/*
* Routing Protocol Instance
@ -145,7 +145,9 @@ struct proto {
struct proto_config *cf; /* Configuration data */
struct proto_config *cf_new; /* Configuration we want to switch to after shutdown (NULL=delete) */
pool *pool; /* Pool containing local objects */
pool *pool_fragile; /* Pool containing fragile local objects which need to be freed
pool *pool_up; /* Pool containing local objects which should be dropped as soon
as the protocol enters the STOP / DOWN state */
pool *pool_inloop; /* Pool containing local objects which need to be freed
before the protocol's birdloop actually stops, like olocks */
event *event; /* Protocol event */
timer *restart_timer; /* Timer to restart the protocol from limits */
@ -209,14 +211,10 @@ struct proto {
*
* rte_recalculate Called at the beginning of the best route selection
* rte_mergable Compare two rte's and decide whether they could be merged (1=yes, 0=no).
* rte_insert Called whenever a rte is inserted to a routing table.
* rte_remove Called whenever a rte is removed from the routing table.
*/
int (*rte_recalculate)(struct rtable_private *, struct network *, struct rte *, struct rte *, struct rte *);
int (*rte_mergable)(struct rte *, struct rte *);
void (*rte_insert)(struct network *, struct rte *);
void (*rte_remove)(struct network *, struct rte *);
u32 (*rte_igp_metric)(const struct rte *);
/* Hic sunt protocol-specific data */
@ -506,7 +504,8 @@ struct channel_class {
#endif
};
extern struct channel_class channel_bgp;
extern const struct channel_class channel_basic;
extern const struct channel_class channel_bgp;
struct channel_config {
node n;
@ -682,6 +681,7 @@ static inline struct channel_config *proto_cf_main_channel(struct proto_config *
struct channel *proto_find_channel_by_table(struct proto *p, rtable *t);
struct channel *proto_find_channel_by_name(struct proto *p, const char *n);
struct channel *proto_add_channel(struct proto *p, struct channel_config *cf);
void proto_remove_channel(struct proto *p, struct channel *c);
int proto_configure_channel(struct proto *p, struct channel **c, struct channel_config *cf);
void channel_set_state(struct channel *c, uint state);

View File

@ -319,7 +319,7 @@ struct rt_pending_export {
struct rt_export_request {
struct rt_export_hook *hook; /* Table part of the export */
char *name; /* Network prefilter address */
char *name;
u8 trace_routes;
uint feed_block_size; /* How many routes to feed at once */
struct rt_prefilter prefilter;

View File

@ -45,7 +45,7 @@
*/
#include "nest/bird.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
#include "nest/cli.h"
@ -91,6 +91,8 @@ const char * const rta_src_names[RTS_MAX] = {
[RTS_PIPE] = "pipe",
[RTS_BABEL] = "Babel",
[RTS_RPKI] = "RPKI",
[RTS_PERF] = "Perf",
[RTS_AGGREGATED] = "aggregated",
};
static void
@ -196,7 +198,7 @@ static struct idm src_ids;
#define RSH_KEY(n) n->private_id
#define RSH_NEXT(n) n->next
#define RSH_EQ(n1,n2) n1 == n2
#define RSH_FN(n) u32_hash(n)
#define RSH_FN(n) u64_hash(n)
#define RSH_REHASH rte_src_rehash
#define RSH_PARAMS /2, *2, 1, 1, 8, 20
@ -255,7 +257,7 @@ rt_get_source_o(struct rte_owner *p, u32 id)
HASH_INSERT2(p->hash, RSH, rta_pool, src);
if (config->table_debug)
log(L_TRACE "Allocated new rte_src for %s, ID %uL %uG, have %u sources now",
log(L_TRACE "Allocated new rte_src for %s, ID %luL %uG, have %u sources now",
p->name, src->private_id, src->global_id, p->uc);
uint gm = atomic_load_explicit(&rte_src_global_max, memory_order_relaxed);
@ -364,6 +366,30 @@ rt_prune_sources(void *data)
RTA_UNLOCK;
}
void
rt_dump_sources(struct rte_owner *o)
{
debug("\t%s: hord=%u, uc=%u, cnt=%u prune=%p, stop=%p\n",
o->name, o->hash.order, o->uc, o->hash.count, o->prune, o->stop);
debug("\tget_route_info=%p, better=%p, mergable=%p, igp_metric=%p, recalculate=%p",
o->class->get_route_info, o->class->rte_better, o->class->rte_mergable,
o->class->rte_igp_metric, o->rte_recalculate);
int splitting = 0;
HASH_WALK(o->hash, next, src)
{
debug("%c%c%uL %uG %luU",
(splitting % 8) ? ',' : '\n',
(splitting % 8) ? ' ' : '\t',
src->private_id, src->global_id,
atomic_load_explicit(&src->uc, memory_order_relaxed));
splitting++;
}
HASH_WALK_END;
debug("\n");
}
void
rt_init_sources(struct rte_owner *o, const char *name, event_list *list)
{
@ -573,16 +599,20 @@ static struct idm ea_class_idm;
/* Config parser lex register function */
void ea_lex_register(struct ea_class *def);
void ea_lex_unregister(struct ea_class *def);
static void
ea_class_free(struct ea_class *cl)
{
RTA_LOCK;
/* No more ea class references. Unregister the attribute. */
idm_free(&ea_class_idm, cl->id);
ea_class_global[cl->id] = NULL;
if (!cl->hidden)
ea_lex_unregister(cl);
/* When we start supporting full protocol removal, we may need to call
* ea_lex_unregister(cl), see where ea_lex_register() is called. */
RTA_UNLOCK;
}
static void
@ -619,7 +649,7 @@ ea_class_init(void)
sizeof(*ea_class_global) * (ea_class_max = EA_CLASS_INITIAL_MAX));
}
static struct ea_class_ref *
struct ea_class_ref *
ea_ref_class(pool *p, struct ea_class *def)
{
def->uc++;
@ -640,9 +670,6 @@ ea_register(pool *p, struct ea_class *def)
ASSERT_DIE(def->id < ea_class_max);
ea_class_global[def->id] = def;
if (!def->hidden)
ea_lex_register(def);
return ea_ref_class(p, def);
}
@ -680,7 +707,12 @@ ea_register_init(struct ea_class *clp)
{
RTA_LOCK;
ASSERT_DIE(!ea_class_find_by_name(clp->name));
ea_register(&root_pool, clp);
struct ea_class *def = ea_register(&root_pool, clp)->class;
if (!clp->hidden)
ea_lex_register(def);
RTA_UNLOCK;
}
@ -1340,7 +1372,15 @@ nexthop_dump(const struct adata *ad)
debug(":");
NEXTHOP_WALK(nh, nhad)
if (!NEXTHOP_IS_REACHABLE(nhad))
{
const char *name = rta_dest_name(nhad->dest);
if (name)
debug(" %s", name);
else
debug(" D%d", nhad->dest);
}
else NEXTHOP_WALK(nh, nhad)
{
if (ipa_nonzero(nh->gw)) debug(" ->%I", nh->gw);
if (nh->labels) debug(" L %d", nh->label[0]);

View File

@ -18,7 +18,7 @@
#include "nest/bird.h"
#include "nest/iface.h"
#include "nest/protocol.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/rt-dev.h"
#include "conf/conf.h"
#include "lib/resource.h"

View File

@ -55,7 +55,7 @@
#undef LOCAL_DEBUG
#include "nest/bird.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "lib/string.h"
/*

240
nest/rt-fib_test.c Normal file
View File

@ -0,0 +1,240 @@
/*
* BIRD -- Forwarding Information Base -- Tests
*
* (c) 2023 CZ.NIC z.s.p.o.
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#include "test/birdtest.h"
#include "test/bt-utils.h"
#include "nest/route.h"
#define TESTS_NUM 10
#define PREFIXES_NUM 400000
#define PREFIX_TESTS_NUM 200000
#define PREFIX_BENCH_MAX 1000000
#define PREFIX_BENCH_NUM 10000000
struct test_node
{
int pos;
struct fib_node n;
};
static inline int net_match(struct test_node *tn, net_addr *query, net_addr *data)
{ return (tn->pos < PREFIXES_NUM) && net_equal(query, &data[tn->pos]); }
static int
t_match_random_net(void)
{
bt_config_parse(BT_CONFIG_SIMPLE);
for (int round = 0; round < TESTS_NUM; round++)
{
int type = !(round & 1) ? NET_IP4 : NET_IP6;
pool *p = rp_new(&root_pool, the_bird_domain.the_bird, "FIB pool");
net_addr *nets = bt_random_nets(type, PREFIXES_NUM);
/* Make FIB structure */
struct fib f;
fib_init(&f, &root_pool, type, sizeof(struct test_node), OFFSETOF(struct test_node, n), 4, NULL);
for (int i = 0; i < PREFIXES_NUM; i++)
{
struct test_node *tn = fib_get(&f, &nets[i]);
bt_assert(!tn->pos || net_match(tn, &nets[i], nets));
tn->pos = i;
}
/* Test (mostly) negative matches */
for (int i = 0; i < PREFIX_TESTS_NUM; i++)
{
net_addr net;
bt_random_net(&net, type);
struct test_node *tn = fib_find(&f, &net);
bt_assert(!tn || net_match(tn, &net, nets));
}
/* Test positive matches */
for (int i = 0; i < PREFIX_TESTS_NUM; i++)
{
int j = bt_random_n(PREFIXES_NUM);
struct test_node *tn = fib_find(&f, &nets[j]);
bt_assert(tn && net_match(tn, &nets[j], nets));
}
rfree(p);
tmp_flush();
}
return 1;
}
static int
t_fib_walk(void)
{
bt_config_parse(BT_CONFIG_SIMPLE);
for (int round = 0; round < TESTS_NUM; round++)
{
int type = !(round & 1) ? NET_IP4 : NET_IP6;
pool *p = rp_new(&root_pool, the_bird_domain.the_bird, "FIB pool");
net_addr *nets = bt_random_nets(type, PREFIXES_NUM);
byte *marks = tmp_allocz(PREFIXES_NUM);
/* Make FIB structure */
struct fib f;
fib_init(&f, p, type, sizeof(struct test_node), OFFSETOF(struct test_node, n), 4, NULL);
for (int i = 1; i < PREFIXES_NUM; i++)
{
struct test_node *tn = fib_get(&f, &nets[i]);
bt_assert(!tn->pos || net_match(tn, &nets[i], nets));
if (tn->pos)
{
/* Mark dupicate nets */
bt_assert(!marks[tn->pos]);
marks[tn->pos] = 1;
}
tn->pos = i;
}
/* Walk FIB and mark nets */
FIB_WALK(&f, struct test_node, tn)
{
bt_assert(!marks[tn->pos]);
marks[tn->pos] = 1;
}
FIB_WALK_END;
/* Check in all nets are marked */
for (int i = 1; i < PREFIXES_NUM; i++)
bt_assert(marks[i]);
rfree(p);
tmp_flush();
}
return 1;
}
static int
benchmark_fib_dataset(const char *filename, int type)
{
net_addr *nets, *test_r, *test_s;
uint n = PREFIX_BENCH_MAX;
int tn = PREFIX_BENCH_NUM;
int match;
bt_reset_suite_case_timer();
bt_log_suite_case_result(1, "Reading %s", filename, n);
nets = bt_read_net_file(filename, type, &n);
bt_log_suite_case_result(1, "Read net data, %u nets", n);
bt_reset_suite_case_timer();
pool *p = rp_new(&root_pool, the_bird_domain.the_bird, "FIB pool");
/* Make FIB structure */
struct fib f;
fib_init(&f, p, type, sizeof(struct test_node), OFFSETOF(struct test_node, n), 0, NULL);
for (int i = 0; i < (int) n; i++)
{
struct test_node *tn = fib_get(&f, &nets[i]);
tn->pos = i;
}
bt_log_suite_case_result(1, "Fill FIB structure, %u nets, order %u", n, f.hash_order);
bt_reset_suite_case_timer();
/* Compute FIB size */
size_t fib_size = rmemsize(p).effective * 1000 / (1024*1024);
bt_log_suite_case_result(1, "FIB size: %u.%03u MB", (uint) (fib_size / 1000), (uint) (fib_size % 1000));
/* Compute FIB histogram */
uint hist[16] = {};
uint sum = 0;
for (uint i = 0; i < f.hash_size; i++)
{
int len = 0;
for (struct fib_node *fn = f.hash_table[i]; fn; fn = fn->next)
len++;
sum += len;
len = MIN(len, 15);
hist[len]++;
}
bt_log_suite_case_result(1, "FIB histogram:");
for (uint i = 0; i < 16; i++)
if (hist[i])
bt_log_suite_case_result(1, "%02u: %8u", i, hist[i]);
uint avg = (sum * 1000) / (f.hash_size - hist[0]);
bt_log_suite_case_result(1, "FIB chain length: %u.%03u", (uint) (avg / 1000), (uint) (avg % 1000));
bt_reset_suite_case_timer();
/* Make test data */
test_r = bt_random_nets(type, tn);
test_s = bt_random_net_subset(nets, n, tn);
bt_log_suite_case_result(1, "Make test data, 2x %u nets", tn);
bt_reset_suite_case_timer();
/* Test (mostly negative) random matches */
match = 0;
for (int i = 0; i < tn; i++)
if (fib_find(&f, &test_r[i]))
match++;
bt_log_suite_case_result(1, "Random match, %d / %d matches", match, tn);
bt_reset_suite_case_timer();
/* Test (positive) subset matches */
match = 0;
for (int i = 0; i < tn; i++)
if (fib_find(&f, &test_s[i]))
match++;
bt_log_suite_case_result(1, "Subset match, %d / %d matches", match, tn);
bt_log_suite_case_result(1, "");
bt_reset_suite_case_timer();
rfree(p);
tmp_flush();
return 1;
}
static int UNUSED
t_bench_fib_datasets(void)
{
bt_config_parse(BT_CONFIG_SIMPLE);
/* Specific datasets, not included */
benchmark_fib_dataset("fib-data-bgp-v4-1", NET_IP4);
benchmark_fib_dataset("fib-data-bgp-v4-10", NET_IP4);
benchmark_fib_dataset("fib-data-bgp-v6-1", NET_IP6);
benchmark_fib_dataset("fib-data-bgp-v6-10", NET_IP6);
return 1;
}
int
main(int argc, char *argv[])
{
bt_init(argc, argv);
bt_bird_init();
bt_test_suite(t_match_random_net, "Testing random prefix matching");
bt_test_suite(t_fib_walk, "Testing FIB_WALK() on random FIB");
// bt_test_suite(t_bench_fib_datasets, "Benchmark FIB from datasets by random subset of nets");
return bt_exit_value();
}

View File

@ -10,7 +10,7 @@
#undef LOCAL_DEBUG
#include "nest/bird.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/cli.h"
#include "nest/iface.h"
@ -79,7 +79,7 @@ rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, int primary
if (d->verbose)
{
ea_show_list(c, a);
cli_printf(c, -1008, "\tInternal route handling values: %uL %uG %uS id %u",
cli_printf(c, -1008, "\tInternal route handling values: %luL %uG %uS id %u",
e->src->private_id, e->src->global_id, e->stale_cycle, e->id);
}
else if (dest == RTD_UNICAST)

View File

@ -93,7 +93,7 @@
#undef LOCAL_DEBUG
#include "nest/bird.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
#include "lib/resource.h"
@ -154,7 +154,7 @@ static void rt_delete(void *);
static void rt_export_used(struct rt_table_exporter *, const char *, const char *);
static void rt_export_cleanup(struct rtable_private *tab);
static int rte_same(const rte *x, const rte *y);
int rte_same(const rte *x, const rte *y);
const char *rt_import_state_name_array[TIS_MAX] = {
[TIS_DOWN] = "DOWN",
@ -735,7 +735,7 @@ rte_mergable(const rte *pri, const rte *sec)
static void
rte_trace(const char *name, const rte *e, int dir, const char *msg)
{
log(L_TRACE "%s %c %s %N src %uL %uG %uS id %u %s",
log(L_TRACE "%s %c %s %N src %luL %uG %uS id %u %s",
name, dir, msg, e->net,
e->src->private_id, e->src->global_id, e->stale_cycle, e->id,
rta_dest_name(rte_dest(e)));
@ -1089,7 +1089,6 @@ rt_notify_merged(struct rt_export_request *req, const net_addr *n,
const rte **feed, uint count)
{
struct channel *c = channel_from_export_request(req);
// struct proto *p = c->proto;
#if 0 /* TODO: Find whether this check is possible when processing multiple changes at once. */
@ -1659,12 +1658,15 @@ rte_validate(struct channel *ch, rte *e)
return 1;
}
static int
int
rte_same(const rte *x, const rte *y)
{
/* rte.flags / rte.pflags are not checked, as they are internal to rtable */
return
x->attrs == y->attrs &&
(x == y) || (
(x->attrs == y->attrs) ||
((!(x->attrs->flags & EALF_CACHED) || !(y->attrs->flags & EALF_CACHED)) && ea_same(x->attrs, y->attrs))
) &&
x->src == y->src &&
rte_is_filtered(x) == rte_is_filtered(y);
}
@ -2492,6 +2494,7 @@ rte_dump(struct rte_storage *e)
{
debug("%-1N ", e->rte.net);
debug("PF=%02x ", e->rte.pflags);
debug("SRC=%uG ", e->rte.src->global_id);
ea_dump(e->rte.attrs);
debug("\n");
}
@ -3434,8 +3437,8 @@ rt_preconfig(struct config *c)
{
init_list(&c->tables);
c->def_tables[NET_IP4] = cf_define_symbol(cf_get_symbol("master4"), SYM_TABLE, table, NULL);
c->def_tables[NET_IP6] = cf_define_symbol(cf_get_symbol("master6"), SYM_TABLE, table, NULL);
c->def_tables[NET_IP4] = cf_define_symbol(c, cf_get_symbol(c, "master4"), SYM_TABLE, table, NULL);
c->def_tables[NET_IP6] = cf_define_symbol(c, cf_get_symbol(c, "master6"), SYM_TABLE, table, NULL);
}
void
@ -4072,7 +4075,7 @@ rt_new_table(struct symbol *s, uint addr_type)
if (s == new_config->def_tables[addr_type])
s->table = c;
else
cf_define_symbol(s, SYM_TABLE, table, c);
cf_define_symbol(new_config, s, SYM_TABLE, table, c);
c->name = s->name;
c->addr_type = addr_type;

View File

@ -2,6 +2,7 @@ H Protocols
C babel
C bfd
C bgp
C bmp
C ospf
C pipe
C radv

1
proto/aggregator/Doc Normal file
View File

@ -0,0 +1 @@
S aggregator.c

View File

@ -0,0 +1,6 @@
src := aggregator.c
obj := $(src-o-files)
$(all-daemon)
$(cf-local)
tests_objs := $(tests_objs) $(src-o-files)

View File

@ -0,0 +1,473 @@
/*
* BIRD Internet Routing Daemon -- Route aggregation
*
* (c) 2023--2023 Igor Putovny <igor.putovny@nic.cz>
* (c) 2023 CZ.NIC, z.s.p.o.
*
* 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/iface.h"
#include "filter/filter.h"
#include "aggregator.h"
#include <stdlib.h>
/*
* Compare list of &f_val entries.
* @count: number of &f_val entries
*/
static int
same_val_list(const struct f_val *v1, const struct f_val *v2, uint len)
{
for (uint i = 0; i < len; i++)
if (!val_same(&v1[i], &v2[i]))
return 0;
return 1;
}
/*
* 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
aggregator_bucket_update(struct aggregator_proto *p, struct aggregator_bucket *bucket, const net_addr *net)
{
/* Empty bucket */
if (!bucket->rte)
{
rte_update(p->dst, net, NULL, bucket->last_src);
rt_unlock_source(bucket->last_src);
bucket->last_src = NULL;
return;
}
/* Store TMP linpool state */
struct lp_state *tmp_state = lp_save(tmp_linpool);
/* Allocate route */
struct rte new = { .net = net, .src = bucket->rte->rte.src };
ea_set_attr(&new.attrs, EA_LITERAL_EMBEDDED(&ea_gen_source, 0, RTS_AGGREGATED));
if (net_type_match(net, NB_DEST))
ea_set_dest(&new.attrs, 0, RTD_UNREACHABLE);
/* Seed the attributes from aggregator rule */
f_eval_rte(p->premerge, &new, p->aggr_on_count, bucket->aggr_data, 0, NULL);
/*
log("=============== CREATE MERGED ROUTE ===============");
log("New route created: id = %d, protocol: %s", new->src->global_id, new->src->proto->name);
log("===================================================");
*/
/* merge filter needs one argument called "routes" */
struct f_val val = {
.type = T_ROUTES_BLOCK,
.val.rte_block = {},
};
for (struct aggregator_route *rte = bucket->rte; rte; rte = rte->next_rte)
val.val.rte_block.len++;
val.val.rte_block.rte = tmp_alloc(sizeof(struct rte *) * val.val.rte_block.len);
{
uint i = 0;
for (struct aggregator_route *rte = bucket->rte; rte; rte = rte->next_rte)
val.val.rte_block.rte[i++] = &rte->rte;
ASSERT_DIE(i == val.val.rte_block.len);
}
/* Actually run the merge rule */
enum filter_return fret = f_eval_rte(p->merge_by, &new, 1, &val, 0, NULL);
/* Finally import the route */
switch (fret)
{
/* Pass the route to the protocol */
case F_ACCEPT:
rte_update(p->dst, net, &new, bucket->last_src ?: new.src);
break;
/* Something bad happened */
default:
ASSERT_DIE(fret == F_ERROR);
/* fall through */
/* We actually don't want this route */
case F_REJECT:
if (bucket->last_src)
rte_update(p->dst, net, NULL, bucket->last_src);
break;
}
/* Switch source lock for bucket->last_src */
if (bucket->last_src != new.src)
{
if (new.src)
rt_lock_source(new.src);
if (bucket->last_src)
rt_unlock_source(bucket->last_src);
bucket->last_src = new.src;
}
lp_restore(tmp_linpool, tmp_state);
}
/*
* Reload all the buckets on reconfiguration if merge filter has changed.
* TODO: make this splitted
*/
static void
aggregator_reload_buckets(void *data)
{
struct aggregator_proto *p = data;
HASH_WALK(p->buckets, next_hash, b)
if (b->rte)
aggregator_bucket_update(p, b, b->rte->rte.net);
HASH_WALK_END;
}
static inline u32 aggr_route_hash(const rte *e)
{
struct {
const net_addr *net; /* the net_addr pointer is stable as long as any route exists for it in the source table */
struct rte_src *src;
} obj = {
.net = e->net,
.src = e->src,
};
return mem_hash(&obj, sizeof obj);
}
#define AGGR_RTE_KEY(n) (&(n)->rte)
#define AGGR_RTE_NEXT(n) ((n)->next_hash)
#define AGGR_RTE_EQ(a,b) (((a)->src == (b)->src) && ((a)->net == (b)->net))
#define AGGR_RTE_FN(_n) aggr_route_hash(_n)
#define AGGR_RTE_ORDER 4 /* Initial */
#define AGGR_RTE_REHASH aggr_rte_rehash
#define AGGR_RTE_PARAMS /8, *2, 2, 2, 4, 24
HASH_DEFINE_REHASH_FN(AGGR_RTE, struct aggregator_route);
#define AGGR_BUCK_KEY(n) (n)
#define AGGR_BUCK_NEXT(n) ((n)->next_hash)
#define AGGR_BUCK_EQ(a,b) (((a)->hash == (b)->hash) && (same_val_list((a)->aggr_data, (b)->aggr_data, p->aggr_on_count)))
#define AGGR_BUCK_FN(n) ((n)->hash)
#define AGGR_BUCK_ORDER 4 /* Initial */
#define AGGR_BUCK_REHASH aggr_buck_rehash
#define AGGR_BUCK_PARAMS /8, *2, 2, 2, 4, 24
HASH_DEFINE_REHASH_FN(AGGR_BUCK, struct aggregator_bucket);
#define AGGR_DATA_MEMSIZE (sizeof(struct f_val) * p->aggr_on_count)
static void
aggregator_rt_notify(struct proto *P, struct channel *src_ch, const net_addr *net, rte *new, const rte *old)
{
struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
ASSERT_DIE(src_ch == p->src);
struct aggregator_bucket *new_bucket = NULL, *old_bucket = NULL;
struct aggregator_route *old_route = NULL;
/* Find the objects for the old route */
if (old)
old_route = HASH_FIND(p->routes, AGGR_RTE, old);
if (old_route)
old_bucket = old_route->bucket;
/* Find the bucket for the new route */
if (new)
{
/* Routes are identical, do nothing */
if (old_route && rte_same(&old_route->rte, new))
return;
/* Evaluate route attributes. */
struct aggregator_bucket *tmp_bucket = sl_allocz(p->bucket_slab);
struct lp_state *tmp_state = lp_save(tmp_linpool);
struct ea_list *oa = new->attrs;
enum filter_return fret = f_eval_rte(p->aggr_on, new, 0, NULL, p->aggr_on_count, tmp_bucket->aggr_data);
if (new->attrs != oa)
log(L_WARN "Aggregator rule modifies the route");
/* Check filter return value */
if (fret > F_RETURN)
{
sl_free(tmp_bucket);
lp_restore(tmp_linpool, tmp_state);
return;
}
/* Compute the hash */
u64 haux;
mem_hash_init(&haux);
for (uint i = 0; i < p->aggr_on_count; i++)
mem_hash_mix_f_val(&haux, &tmp_bucket->aggr_data[i]);
tmp_bucket->hash = mem_hash_value(&haux);
/* Find the existing bucket */
if (new_bucket = HASH_FIND(p->buckets, AGGR_BUCK, tmp_bucket))
sl_free(tmp_bucket);
else
{
new_bucket = tmp_bucket;
HASH_INSERT2(p->buckets, AGGR_BUCK, p->p.pool, new_bucket);
}
/* Store the route attributes */
if (rta_is_cached(new->attrs))
rta_clone(new->attrs);
else
new->attrs = rta_lookup(new->attrs, 0);
/* Insert the new route into the bucket */
struct aggregator_route *arte = sl_alloc(p->route_slab);
*arte = (struct aggregator_route) {
.bucket = new_bucket,
.rte = *new,
.next_rte = new_bucket->rte,
};
new_bucket->rte = arte;
new_bucket->count++;
HASH_INSERT2(p->routes, AGGR_RTE, p->p.pool, arte);
lp_restore(tmp_linpool, tmp_state);
}
/* Remove the old route from its bucket */
if (old_bucket)
{
for (struct aggregator_route **k = &old_bucket->rte; *k; k = &(*k)->next_rte)
if (*k == old_route)
{
*k = (*k)->next_rte;
break;
}
old_bucket->count--;
HASH_REMOVE2(p->routes, AGGR_RTE, p->p.pool, old_route);
rta_free(old_route->rte.attrs);
sl_free(old_route);
}
/* Announce changes */
if (old_bucket)
aggregator_bucket_update(p, old_bucket, net);
if (new_bucket && (new_bucket != old_bucket))
aggregator_bucket_update(p, new_bucket, net);
/* Cleanup the old bucket if empty */
if (old_bucket && (!old_bucket->rte || !old_bucket->count))
{
ASSERT_DIE(!old_bucket->rte && !old_bucket->count);
HASH_REMOVE2(p->buckets, AGGR_BUCK, p->p.pool, old_bucket);
sl_free(old_bucket);
}
}
static int
aggregator_preexport(struct channel *C, struct rte *new)
{
struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, C->proto);
/* Reject our own routes */
if (new->sender == p->dst->in_req.hook)
return -1;
/* Disallow aggregating already aggregated routes */
if (ea_get_int(new->attrs, &ea_gen_source, 0) == RTS_AGGREGATED)
{
log(L_ERR "Multiple aggregations of the same route not supported.");
return -1;
}
return 0;
}
static void
aggregator_postconfig(struct proto_config *CF)
{
struct aggregator_config *cf = SKIP_BACK(struct aggregator_config, c, CF);
if (!cf->dst->table)
cf_error("Source table not specified");
if (!cf->src->table)
cf_error("Destination table not specified");
if (cf->dst->table->addr_type != cf->src->table->addr_type)
cf_error("Both tables must be of the same type");
cf->dst->in_filter = cf->src->in_filter;
cf->src->in_filter = FILTER_REJECT;
cf->dst->out_filter = FILTER_REJECT;
cf->dst->debug = cf->src->debug;
}
static struct proto *
aggregator_init(struct proto_config *CF)
{
struct proto *P = proto_new(CF);
struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
struct aggregator_config *cf = SKIP_BACK(struct aggregator_config, c, CF);
proto_configure_channel(P, &p->src, cf->src);
proto_configure_channel(P, &p->dst, cf->dst);
p->aggr_on_count = cf->aggr_on_count;
p->aggr_on = cf->aggr_on;
p->premerge = cf->premerge;
p->merge_by = cf->merge_by;
P->rt_notify = aggregator_rt_notify;
P->preexport = aggregator_preexport;
return P;
}
static int
aggregator_start(struct proto *P)
{
struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
p->bucket_slab = sl_new(P->pool, sizeof(struct aggregator_bucket) + AGGR_DATA_MEMSIZE);
HASH_INIT(p->buckets, P->pool, AGGR_BUCK_ORDER);
p->route_slab = sl_new(P->pool, sizeof(struct aggregator_route));
HASH_INIT(p->routes, P->pool, AGGR_RTE_ORDER);
p->reload_buckets = (event) {
.hook = aggregator_reload_buckets,
.data = p,
};
return PS_UP;
}
static int
aggregator_shutdown(struct proto *P)
{
struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
HASH_WALK_DELSAFE(p->buckets, next_hash, b)
{
for (struct aggregator_route *arte; arte = b->rte; )
{
b->rte = arte->next_rte;
b->count--;
HASH_REMOVE(p->routes, AGGR_RTE, arte);
rta_free(arte->rte.attrs);
sl_free(arte);
}
if (b->last_src)
rt_unlock_source(b->last_src);
ASSERT_DIE(b->count == 0);
HASH_REMOVE(p->buckets, AGGR_BUCK, b);
sl_free(b);
}
HASH_WALK_END;
return PS_DOWN;
}
static int
aggregator_reconfigure(struct proto *P, struct proto_config *CF)
{
struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
struct aggregator_config *cf = SKIP_BACK(struct aggregator_config, c, CF);
TRACE(D_EVENTS, "Reconfiguring");
/* Compare numeric values (shortcut) */
if (cf->aggr_on_count != p->aggr_on_count)
return 0;
/* Compare aggregator rule */
if (!f_same(cf->aggr_on, p->aggr_on) || !f_same(cf->premerge, p->premerge))
return 0;
/* Compare merge filter */
if (!f_same(cf->merge_by, p->merge_by))
ev_schedule(&p->reload_buckets);
p->aggr_on = cf->aggr_on;
p->premerge = cf->premerge;
p->merge_by = cf->merge_by;
return 1;
}
struct protocol proto_aggregator = {
.name = "Aggregator",
.template = "aggregator%d",
.preference = 1,
.channel_mask = NB_ANY,
.proto_size = sizeof(struct aggregator_proto),
.config_size = sizeof(struct aggregator_config),
.startup = PROTOCOL_STARTUP_CONNECTOR,
.postconfig = aggregator_postconfig,
.init = aggregator_init,
.start = aggregator_start,
.shutdown = aggregator_shutdown,
.reconfigure = aggregator_reconfigure,
};
void
aggregator_build(void)
{
proto_build(&proto_aggregator);
}

View File

@ -0,0 +1,69 @@
/*
* BIRD -- Aggregator Pseudoprotocol
*
* (c) 2023 Igor Putovny <igor.putovny@nic.cz>
* (c) 2023 Maria Matejka <mq@ucw.cz>
* (c) 2023 CZ.NIC z.s.p.o.
*
* Can be freely distributed and used under the terms of the GNU GPL.
*
* This file contains the data structures used by Babel.
*/
#ifndef _BIRD_AGGREGATOR_H_
#define _BIRD_AGGREGATOR_H_
#include "nest/bird.h"
#include "nest/protocol.h"
#include "lib/hash.h"
struct aggregator_config {
struct proto_config c;
struct channel_config *src, *dst;
const struct f_line *aggr_on;
const struct f_line *premerge;
const struct f_line *merge_by;
uint aggr_on_count;
u8 aggr_on_net;
};
struct aggregator_route {
struct aggregator_route *next_hash;
struct aggregator_route *next_rte;
struct aggregator_bucket *bucket;
struct rte rte;
};
struct aggregator_bucket {
struct aggregator_bucket *next_hash;
struct aggregator_route *rte; /* Pointer to struct aggregator_route.rte */
struct rte_src *last_src; /* Which src we announced the bucket last with */
u32 count;
u32 hash;
struct f_val aggr_data[0];
};
struct aggregator_proto {
struct proto p;
struct channel *src, *dst;
/* Buckets by aggregator rule */
HASH(struct aggregator_bucket) buckets;
slab *bucket_slab;
/* Routes by net and src */
HASH(struct aggregator_route) routes;
slab *route_slab;
/* Aggregator rule */
const struct f_line *aggr_on;
uint aggr_on_count;
u8 aggr_on_net;
/* Merge filter */
const struct f_line *premerge;
const struct f_line *merge_by;
event reload_buckets;
};
#endif

151
proto/aggregator/config.Y Normal file
View File

@ -0,0 +1,151 @@
/*
* BIRD -- Aggregator configuration
*
* (c) 2023 Igor Putovny <igor.putovny@nic.cz>
* (c) 2023 Maria Matejka <mq@ucw.cz>
* (c) 2023 CZ.NIC z.s.p.o.
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
CF_HDR
#include "proto/aggregator/aggregator.h"
CF_DEFINES
#define AGGREGATOR_CFG ((struct aggregator_config *) this_proto)
#define AGGR_ITEM_ALLOC ((struct aggr_item_node *) cfg_allocz(sizeof(struct aggr_item_node)))
CF_DECLS
CF_KEYWORDS(AGGREGATOR, AGGREGATE, ON, MERGE, BY)
%type <xp> aggr_item aggr_list
CF_GRAMMAR
proto: aggregator_proto ;
aggregator_proto_start: proto_start AGGREGATOR
{
this_proto = proto_config_new(&proto_aggregator, $1);
this_channel = AGGREGATOR_CFG->src = channel_config_new(NULL, "source", 0, this_proto);
AGGREGATOR_CFG->dst = channel_config_new(NULL, "destination", 0, this_proto);
AGGREGATOR_CFG->src->ra_mode = AGGREGATOR_CFG->dst->ra_mode = RA_ANY;
};
aggregator_proto_item:
proto_item
| channel_item_
| PEER TABLE rtable { AGGREGATOR_CFG->dst->table = $3; }
| AGGREGATE ON {
if (AGGREGATOR_CFG->aggr_on)
cf_error("Only one aggregate on clause allowed");
cf_enter_filters();
cf_push_block_scope(new_config);
} aggr_list {
int count = new_config->current_scope->slots;
cf_pop_block_scope(new_config);
cf_exit_filters();
if (!AGGREGATOR_CFG->aggr_on_net)
cf_error("aggregate on must be always include 'net'.");
struct f_inst *rot = NULL;
while ($4.begin)
{
struct f_inst *tmp = $4.begin->next;
$4.begin->next = rot;
rot = $4.begin;
$4.begin = tmp;
}
AGGREGATOR_CFG->aggr_on_count = count;
AGGREGATOR_CFG->aggr_on = f_linearize(rot, count);
struct f_line *premerge = f_linearize($4.end, 0);
premerge->args = count;
AGGREGATOR_CFG->premerge = premerge;
}
| MERGE BY {
cf_enter_filters();
cf_push_block_scope(new_config);
f_predefined_variable(new_config, "routes", T_ROUTES_BLOCK);
} function_body {
cf_pop_block_scope(new_config);
cf_exit_filters();
$4->args++;
AGGREGATOR_CFG->merge_by = $4;
}
;
aggregator_proto_opts: /* empty */ | aggregator_proto_opts aggregator_proto_item ';' ;
aggregator_proto: aggregator_proto_start proto_name '{' aggregator_proto_opts '}' ;
aggr_list:
aggr_item
| aggr_list ',' aggr_item {
if ($$.begin = $3.begin)
$$.begin->next = $1.begin;
else
$$.begin = $1.begin;
if ($$.end = $3.end)
$$.end->next = $1.end;
else
$$.end = $1.end;
}
;
aggr_item:
'(' term ')' {
switch ($2->type) {
case T_INT:
case T_BOOL:
case T_PAIR:
case T_QUAD:
case T_ENUM:
case T_IP:
case T_EC:
case T_LC:
case T_RD:
/* Fits, OK */
break;
default:
cf_error("Expression evaluated to type %s unsupported by aggregator. Store this value as a custom attribute instead", f_type_name($2->type));
}
$$.begin = $2;
$$.end = NULL;
f_new_var(new_config->current_scope);
}
| lvalue {
$$.begin = f_lval_getter(&$1);
int vari = f_new_var(new_config->current_scope);
if ($1.type == F_LVAL_SA && $1.sa.sa_code == SA_NET)
AGGREGATOR_CFG->aggr_on_net = 1;
if (($1.type == F_LVAL_CONSTANT) ||
($1.type == F_LVAL_SA && $1.sa.readonly))
$$.end = NULL;
else
{
char varname[12];
bsnprintf(varname, 12, "!aggr%d", vari);
$$.end = f_lval_setter(&$1,
f_new_inst(FI_VAR_GET, cf_define_symbol(
new_config, cf_get_symbol(new_config, varname),
SYM_VARIABLE | $$.begin->type, offset, vari
)));
}
}
;
CF_CODE
CF_END

132
proto/aggregator/test.conf Normal file
View File

@ -0,0 +1,132 @@
log "bird.log" all;
attribute int foo;
protocol device {}
protocol static {
ipv6 {
import filter {
preference = 111;
foo = 666;
accept;
};
};
route 2001:db8:0::/48 unreachable { bgp_path.prepend(65432); bgp_path.prepend(4200000000); };
route 2001:db8:1::/48 unreachable;
route 2001:db8:2::/48 unreachable;
route 2001:db8:3::/48 unreachable;
route 2001:db8:4::/48 unreachable;
route 2001:db8:5::/48 unreachable;
route 2001:db8:6::/48 unreachable;
route 2001:db8:7::/48 unreachable;
route 2001:db8:8::/48 unreachable;
route 2001:db8:9::/48 unreachable;
route 2001:db8:a::/48 unreachable;
route 2001:db8:b::/48 unreachable;
route 2001:db8:c::/48 unreachable;
route 2001:db8:d::/48 unreachable;
route 2001:db8:e::/48 unreachable;
route 2001:db8:f::/48 unreachable;
}
protocol static {
ipv6 {
import filter {
preference = 222;
foo = 666;
bgp_med = 1;
bgp_community = -empty-.add((65533,1)).add((65500,0xe));
accept;
};
};
route 2001:db8:1::/48 unreachable;
route 2001:db8:3::/48 unreachable;
route 2001:db8:5::/48 unreachable;
route 2001:db8:7::/48 unreachable;
route 2001:db8:9::/48 unreachable;
route 2001:db8:b::/48 unreachable;
route 2001:db8:d::/48 unreachable;
route 2001:db8:f::/48 unreachable;
}
protocol static {
ipv6 {
import filter {
preference = 222;
foo = 666;
bgp_med = 2;
bgp_community = -empty-.add((65533,2)).add((65500,0xd));
accept;
};
};
route 2001:db8:2::/48 unreachable;
route 2001:db8:3::/48 unreachable;
route 2001:db8:6::/48 unreachable;
route 2001:db8:7::/48 unreachable;
route 2001:db8:a::/48 unreachable;
route 2001:db8:b::/48 unreachable;
route 2001:db8:e::/48 unreachable;
route 2001:db8:f::/48 unreachable;
}
protocol static {
ipv6 {
import filter {
preference = 222;
foo = 666;
bgp_med = 4;
bgp_community = -empty-.add((65533,4)).add((65500,0xb));
accept;
};
};
route 2001:db8:4::/48 unreachable;
route 2001:db8:5::/48 unreachable;
route 2001:db8:6::/48 unreachable;
route 2001:db8:7::/48 unreachable;
route 2001:db8:c::/48 unreachable;
route 2001:db8:d::/48 unreachable;
route 2001:db8:e::/48 unreachable;
route 2001:db8:f::/48 unreachable;
}
protocol static {
ipv6 {
import filter {
preference = 222;
foo = 666;
bgp_med = 8;
bgp_community = -empty-.add((65533,8)).add((65500,0x7));
accept;
};
};
route 2001:db8:8::/48 unreachable;
route 2001:db8:9::/48 unreachable;
route 2001:db8:a::/48 unreachable;
route 2001:db8:b::/48 unreachable;
route 2001:db8:c::/48 unreachable;
route 2001:db8:d::/48 unreachable;
route 2001:db8:e::/48 unreachable;
route 2001:db8:f::/48 unreachable;
}
ipv6 table agr_result;
protocol aggregator {
table master6;
peer table agr_result;
export all;
aggregate on net,(defined(bgp_med)), (1 + 3 + 5 + 7), preference, dest;
merge by {
print "Merging all these: ", routes;
bgp_med = 0;
for route r in routes do {
if ! defined(r.bgp_med) then { unset(bgp_med); accept; }
print r, " bgp_med: ", r.bgp_med;
bgp_med = bgp_med + r.bgp_med;
bgp_community = bgp_community.add(r.bgp_community);
}
accept;
};
}

View File

@ -599,6 +599,7 @@ babel_update_cost(struct babel_neighbor *nbr)
switch (cf->type)
{
case BABEL_IFACE_TYPE_WIRED:
case BABEL_IFACE_TYPE_TUNNEL:
/* k-out-of-j selection - Appendix 2.1 in the RFC. */
/* Link is bad if less than cf->limit/16 of expected hellos were received */
@ -627,6 +628,24 @@ babel_update_cost(struct babel_neighbor *nbr)
break;
}
if (cf->rtt_cost && nbr->srtt > cf->rtt_min)
{
uint rtt_cost = cf->rtt_cost;
if (nbr->srtt < cf->rtt_max)
{
uint rtt_interval = cf->rtt_max TO_US - cf->rtt_min TO_US;
uint rtt_diff = (nbr->srtt TO_US - cf->rtt_min TO_US);
rtt_cost = (rtt_cost * rtt_diff) / rtt_interval;
}
txcost = MIN(txcost + rtt_cost, BABEL_INFINITY);
TRACE(D_EVENTS, "Added RTT cost %u to nbr %I on %s with srtt %t ms",
rtt_cost, nbr->addr, nbr->ifa->iface->name, nbr->srtt * 1000);
}
done:
/* If RX cost changed, send IHU with next Hello */
if (rxcost != nbr->rxcost)
@ -853,6 +872,12 @@ babel_build_ihu(union babel_msg *msg, struct babel_iface *ifa, struct babel_neig
msg->ihu.rxcost = n->rxcost;
msg->ihu.interval = ifa->cf->ihu_interval;
if (n->last_tstamp_rcvd && ifa->cf->rtt_send)
{
msg->ihu.tstamp = n->last_tstamp;
msg->ihu.tstamp_rcvd = n->last_tstamp_rcvd TO_US;
}
TRACE(D_PACKETS, "Sending IHU for %I with rxcost %d interval %t",
msg->ihu.addr, msg->ihu.rxcost, (btime) msg->ihu.interval);
}
@ -892,6 +917,9 @@ babel_send_hello(struct babel_iface *ifa, uint interval)
msg.hello.seqno = ifa->hello_seqno++;
msg.hello.interval = interval ?: ifa->cf->hello_interval;
if (ifa->cf->rtt_send)
msg.hello.tstamp = 1; /* real timestamp will be set on TLV write */
TRACE(D_PACKETS, "Sending hello on %s with seqno %d interval %t",
ifa->ifname, msg.hello.seqno, (btime) msg.hello.interval);
@ -1198,14 +1226,26 @@ babel_handle_hello(union babel_msg *m, struct babel_iface *ifa)
msg->seqno, (btime) msg->interval);
struct babel_neighbor *n = babel_get_neighbor(ifa, msg->sender);
struct babel_iface_config *cf = n->ifa->cf;
int first_hello = !n->hello_cnt;
if (msg->tstamp)
{
n->last_tstamp = msg->tstamp;
n->last_tstamp_rcvd = msg->pkt_received;
}
babel_update_hello_history(n, msg->seqno, msg->interval);
babel_update_cost(n);
/* Speed up session establishment by sending IHU immediately */
if (first_hello)
babel_send_ihu(ifa, n);
{
/* if using RTT, all IHUs must be paired with hellos */
if(cf->rtt_send)
babel_send_hello(ifa, 0);
else
babel_send_ihu(ifa, n);
}
}
void
@ -1224,6 +1264,39 @@ babel_handle_ihu(union babel_msg *m, struct babel_iface *ifa)
struct babel_neighbor *n = babel_get_neighbor(ifa, msg->sender);
n->txcost = msg->rxcost;
n->ihu_expiry = current_time() + BABEL_IHU_EXPIRY_FACTOR(msg->interval);
if (msg->tstamp)
{
u32 rtt_sample = 0, pkt_received = msg->pkt_received TO_US;
int remote_time, full_time;
/* processing time reported by peer */
remote_time = (n->last_tstamp - msg->tstamp_rcvd);
/* time since we sent the last timestamp - RTT including remote time */
full_time = (pkt_received - msg->tstamp);
/* sanity checks */
if (remote_time < 0 || full_time < 0 ||
remote_time US_ > BABEL_RTT_MAX_VALUE || full_time US_ > BABEL_RTT_MAX_VALUE)
goto out;
if (remote_time < full_time)
rtt_sample = full_time - remote_time;
if (n->srtt)
{
uint decay = n->ifa->cf->rtt_decay;
n->srtt = (decay * rtt_sample + (256 - decay) * n->srtt) / 256;
}
else
n->srtt = rtt_sample;
TRACE(D_EVENTS, "RTT sample for neighbour %I on %s: %u us (srtt %t ms)",
n->addr, ifa->ifname, rtt_sample, n->srtt * 1000);
}
out:
babel_update_cost(n);
}
@ -1989,7 +2062,7 @@ babel_reconfigure_ifaces(struct babel_proto *p, struct babel_config *cf)
{
IFACE_WALK(iface)
{
if (p->p.vrf && p->p.vrf != iface->master)
if (p->p.vrf && !if_in_vrf(iface, p->p.vrf))
continue;
if (!(iface->flags & IF_UP))
@ -2203,8 +2276,8 @@ babel_show_neighbors(struct proto *P, const char *iff)
}
cli_msg(-1024, "%s:", p->p.name);
cli_msg(-1024, "%-25s %-10s %6s %6s %6s %7s %4s",
"IP address", "Interface", "Metric", "Routes", "Hellos", "Expires", "Auth");
cli_msg(-1024, "%-25s %-10s %6s %6s %6s %7s %4s %9s",
"IP address", "Interface", "Metric", "Routes", "Hellos", "Expires", "Auth", "RTT (ms)");
WALK_LIST(ifa, p->interfaces)
{
@ -2219,9 +2292,10 @@ babel_show_neighbors(struct proto *P, const char *iff)
uint hellos = u32_popcount(n->hello_map);
btime timer = (n->hello_expiry ?: n->init_expiry) - current_time();
cli_msg(-1024, "%-25I %-10s %6u %6u %6u %7t %-4s",
cli_msg(-1024, "%-25I %-10s %6u %6u %6u %7t %-4s %9t",
n->addr, ifa->iface->name, n->cost, rts, hellos, MAX(timer, 0),
n->auth_passed ? "Yes" : "No");
n->auth_passed ? "Yes" : "No",
n->srtt * 1000);
}
}
}

View File

@ -16,7 +16,7 @@
#include "nest/bird.h"
#include "nest/cli.h"
#include "nest/iface.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/locks.h"
#include "nest/password.h"
@ -49,10 +49,16 @@
#define BABEL_GARBAGE_INTERVAL (300 S_)
#define BABEL_RXCOST_WIRED 96
#define BABEL_RXCOST_WIRELESS 256
#define BABEL_RXCOST_RTT 96
#define BABEL_INITIAL_HOP_COUNT 255
#define BABEL_MAX_SEND_INTERVAL 5 /* Unused ? */
#define BABEL_INITIAL_NEIGHBOR_TIMEOUT (60 S_)
#define BABEL_RTT_MAX_VALUE (600 S_)
#define BABEL_RTT_MIN (10 MS_)
#define BABEL_RTT_MAX (120 MS_)
#define BABEL_RTT_DECAY 42
/* Max interval that will not overflow when carried as 16-bit centiseconds */
#define BABEL_TIME_UNITS 10000 /* On-wire times are counted in centiseconds */
#define BABEL_MIN_INTERVAL (0x0001 * BABEL_TIME_UNITS)
@ -92,6 +98,8 @@ enum babel_tlv_type {
enum babel_subtlv_type {
BABEL_SUBTLV_PAD1 = 0,
BABEL_SUBTLV_PADN = 1,
BABEL_SUBTLV_DIVERSITY = 2, /* we don't support this */
BABEL_SUBTLV_TIMESTAMP = 3,
/* Mandatory subtlvs */
BABEL_SUBTLV_SOURCE_PREFIX = 128,
@ -102,6 +110,7 @@ enum babel_iface_type {
BABEL_IFACE_TYPE_UNDEF = 0,
BABEL_IFACE_TYPE_WIRED = 1,
BABEL_IFACE_TYPE_WIRELESS = 2,
BABEL_IFACE_TYPE_TUNNEL = 3,
BABEL_IFACE_TYPE_MAX
};
@ -137,6 +146,12 @@ struct babel_iface_config {
uint ihu_interval; /* IHU interval, in us */
uint update_interval; /* Update interval, in us */
btime rtt_min; /* rtt above which to start penalising metric */
btime rtt_max; /* max rtt metric penalty applied above this */
u16 rtt_cost; /* metric penalty to apply at rtt_max */
u16 rtt_decay; /* decay of neighbour RTT (units of 1/256) */
u8 rtt_send; /* whether to send timestamps on this interface */
u16 rx_buffer; /* RX buffer size, 0 for MTU */
u16 tx_length; /* TX packet length limit (including headers), 0 for MTU */
int tx_tos;
@ -225,6 +240,10 @@ struct babel_neighbor {
u16 next_hello_seqno;
uint last_hello_int;
u32 last_tstamp;
btime last_tstamp_rcvd;
btime srtt;
u32 auth_pc_unicast;
u32 auth_pc_multicast;
u8 auth_passed;
@ -323,6 +342,8 @@ struct babel_msg_hello {
u16 seqno;
uint interval;
ip_addr sender;
u32 tstamp;
btime pkt_received;
};
struct babel_msg_ihu {
@ -332,6 +353,9 @@ struct babel_msg_ihu {
uint interval;
ip_addr addr;
ip_addr sender;
u32 tstamp;
u32 tstamp_rcvd;
btime pkt_received;
};
struct babel_msg_update {

View File

@ -26,7 +26,7 @@ CF_KEYWORDS(BABEL, INTERFACE, METRIC, RXCOST, HELLO, UPDATE, INTERVAL, PORT,
TYPE, WIRED, WIRELESS, RX, TX, BUFFER, PRIORITY, LENGTH, CHECK, LINK,
NEXT, HOP, IPV4, IPV6, SHOW, INTERFACES, NEIGHBORS,
ENTRIES, RANDOMIZE, ROUTER, ID, AUTHENTICATION, NONE, MAC, PERMISSIVE,
EXTENDED)
EXTENDED, TUNNEL, RTT, MIN, MAX, DECAY, SEND, TIMESTAMPS)
CF_GRAMMAR
@ -67,6 +67,10 @@ babel_iface_start:
BABEL_IFACE->limit = BABEL_HELLO_LIMIT;
BABEL_IFACE->tx_tos = IP_PREC_INTERNET_CONTROL;
BABEL_IFACE->tx_priority = sk_priority_control;
BABEL_IFACE->rtt_min = BABEL_RTT_MIN;
BABEL_IFACE->rtt_max = BABEL_RTT_MAX;
BABEL_IFACE->rtt_decay = BABEL_RTT_DECAY;
BABEL_IFACE->rtt_send = 1;
BABEL_IFACE->check_link = 1;
BABEL_IFACE->ext_next_hop = 1;
};
@ -87,8 +91,16 @@ babel_iface_finish:
BABEL_IFACE->hello_interval = BABEL_HELLO_INTERVAL_WIRED;
if (!BABEL_IFACE->rxcost)
BABEL_IFACE->rxcost = BABEL_RXCOST_WIRED;
if (BABEL_IFACE->type == BABEL_IFACE_TYPE_TUNNEL && !BABEL_IFACE->rtt_cost)
BABEL_IFACE->rtt_cost = BABEL_RXCOST_RTT;
}
if (BABEL_IFACE->rtt_cost && !BABEL_IFACE->rtt_send)
cf_error("Can't set RTT cost when sending timestamps is disabled");
if (BABEL_IFACE->rtt_min >= BABEL_IFACE->rtt_max)
cf_error("Min RTT must be smaller than max RTT");
/* Make sure we do not overflow the 16-bit centisec fields */
if (!BABEL_IFACE->update_interval)
BABEL_IFACE->update_interval = MIN_(BABEL_IFACE->hello_interval*BABEL_UPDATE_INTERVAL_FACTOR, BABEL_MAX_INTERVAL);
@ -136,6 +148,7 @@ babel_iface_item:
| LIMIT expr { BABEL_IFACE->limit = $2; if (($2<1) || ($2>16)) cf_error("Limit must be in range 1-16"); }
| TYPE WIRED { BABEL_IFACE->type = BABEL_IFACE_TYPE_WIRED; }
| TYPE WIRELESS { BABEL_IFACE->type = BABEL_IFACE_TYPE_WIRELESS; }
| TYPE TUNNEL { BABEL_IFACE->type = BABEL_IFACE_TYPE_TUNNEL; }
| HELLO INTERVAL expr_us { BABEL_IFACE->hello_interval = $3; if (($3<BABEL_MIN_INTERVAL) || ($3>BABEL_MAX_INTERVAL)) cf_error("Hello interval must be in range 10 ms - 655 s"); }
| UPDATE INTERVAL expr_us { BABEL_IFACE->update_interval = $3; if (($3<BABEL_MIN_INTERVAL) || ($3>BABEL_MAX_INTERVAL)) cf_error("Update interval must be in range 10 ms - 655 s"); }
| RX BUFFER expr { BABEL_IFACE->rx_buffer = $3; if (($3<256) || ($3>65535)) cf_error("RX buffer must be in range 256-65535"); }
@ -149,6 +162,11 @@ babel_iface_item:
| AUTHENTICATION NONE { BABEL_IFACE->auth_type = BABEL_AUTH_NONE; }
| AUTHENTICATION MAC { BABEL_IFACE->auth_type = BABEL_AUTH_MAC; BABEL_IFACE->auth_permissive = 0; }
| AUTHENTICATION MAC PERMISSIVE { BABEL_IFACE->auth_type = BABEL_AUTH_MAC; BABEL_IFACE->auth_permissive = 1; }
| RTT MIN expr_us { BABEL_IFACE->rtt_min = $3; }
| RTT MAX expr_us { BABEL_IFACE->rtt_max = $3; }
| RTT COST expr { BABEL_IFACE->rtt_cost = $3; if ($3 >= BABEL_INFINITY) cf_error("RTT cost must be < 65535"); }
| RTT DECAY expr { BABEL_IFACE->rtt_decay = $3; if (($3 < 1) || ($3 > 256)) cf_error("RTT decay must be between 1-256"); }
| SEND TIMESTAMPS bool { BABEL_IFACE->rtt_send = $3; }
| password_list
;

View File

@ -58,6 +58,13 @@ struct babel_tlv_ihu {
u8 addr[0];
} PACKED;
struct babel_subtlv_timestamp {
u8 type;
u8 length;
u32 tstamp;
u32 tstamp_rcvd; /* only used in IHU */
} PACKED;
struct babel_tlv_router_id {
u8 type;
u8 length;
@ -161,6 +168,7 @@ struct babel_parse_state {
const struct babel_tlv_data* (*get_subtlv_data)(u8 type);
struct babel_proto *proto;
struct babel_iface *ifa;
btime received_time;
ip_addr saddr;
ip_addr next_hop_ip4;
ip_addr next_hop_ip6;
@ -172,6 +180,7 @@ struct babel_parse_state {
u8 def_ip6_prefix_seen; /* def_ip6_prefix is valid */
u8 def_ip4_prefix_seen; /* def_ip4_prefix is valid */
u8 def_ip4_via_ip6_prefix_seen; /* def_ip4_via_ip6_prefix is valid */
u8 hello_tstamp_seen; /* pkt contains a hello timestamp */
u8 current_tlv_endpos; /* End of self-terminating TLVs (offset from start) */
u8 sadr_enabled;
u8 is_unicast;
@ -336,6 +345,7 @@ static int babel_read_update(struct babel_tlv *hdr, union babel_msg *msg, struct
static int babel_read_route_request(struct babel_tlv *hdr, union babel_msg *msg, struct babel_parse_state *state);
static int babel_read_seqno_request(struct babel_tlv *hdr, union babel_msg *msg, struct babel_parse_state *state);
static int babel_read_source_prefix(struct babel_tlv *hdr, union babel_msg *msg, struct babel_parse_state *state);
static int babel_read_timestamp(struct babel_tlv *hdr, union babel_msg *msg, struct babel_parse_state *state);
static uint babel_write_ack(struct babel_tlv *hdr, union babel_msg *msg, struct babel_write_state *state, uint max_len);
static uint babel_write_hello(struct babel_tlv *hdr, union babel_msg *msg, struct babel_write_state *state, uint max_len);
@ -344,6 +354,7 @@ static uint babel_write_update(struct babel_tlv *hdr, union babel_msg *msg, stru
static uint babel_write_route_request(struct babel_tlv *hdr, union babel_msg *msg, struct babel_write_state *state, uint max_len);
static uint babel_write_seqno_request(struct babel_tlv *hdr, union babel_msg *msg, struct babel_write_state *state, uint max_len);
static int babel_write_source_prefix(struct babel_tlv *hdr, net_addr *net, uint max_len);
static int babel_write_timestamp(struct babel_tlv *hdr, u32 tstamp, u32 tstamp_rcvd, uint max_len);
static const struct babel_tlv_data tlv_data[BABEL_TLV_MAX] = {
[BABEL_TLV_ACK_REQ] = {
@ -419,6 +430,13 @@ static const struct babel_tlv_data *get_packet_tlv_data(u8 type)
return type < sizeof(tlv_data) / sizeof(*tlv_data) ? &tlv_data[type] : NULL;
}
static const struct babel_tlv_data timestamp_tlv_data = {
sizeof(struct babel_subtlv_timestamp),
babel_read_timestamp,
NULL,
NULL
};
static const struct babel_tlv_data source_prefix_tlv_data = {
sizeof(struct babel_subtlv_source_prefix),
babel_read_source_prefix,
@ -430,6 +448,8 @@ static const struct babel_tlv_data *get_packet_subtlv_data(u8 type)
{
switch (type)
{
case BABEL_SUBTLV_TIMESTAMP:
return &timestamp_tlv_data;
case BABEL_SUBTLV_SOURCE_PREFIX:
return &source_prefix_tlv_data;
@ -491,16 +511,34 @@ babel_read_hello(struct babel_tlv *hdr, union babel_msg *m,
static uint
babel_write_hello(struct babel_tlv *hdr, union babel_msg *m,
struct babel_write_state *state UNUSED, uint max_len UNUSED)
struct babel_write_state *state UNUSED, uint max_len)
{
struct babel_tlv_hello *tlv = (void *) hdr;
struct babel_msg_hello *msg = &m->hello;
uint len = sizeof(struct babel_tlv_hello);
TLV_HDR0(tlv, BABEL_TLV_HELLO);
put_u16(&tlv->seqno, msg->seqno);
put_time16(&tlv->interval, msg->interval);
return sizeof(struct babel_tlv_hello);
if (msg->tstamp)
{
/*
* There can be a substantial delay between when the babel_msg was created
* and when it is serialised. We don't want this included in the RTT
* measurement, so replace the timestamp with the current time to get as
* close as possible to on-wire time for the packet.
*/
u32 tstamp = current_time_now() TO_US;
int l = babel_write_timestamp(hdr, tstamp, 0, max_len);
if (l < 0)
return 0;
len += l;
}
return len;
}
static int
@ -565,6 +603,7 @@ babel_write_ihu(struct babel_tlv *hdr, union babel_msg *m,
{
struct babel_tlv_ihu *tlv = (void *) hdr;
struct babel_msg_ihu *msg = &m->ihu;
uint len = sizeof(*tlv);
if (ipa_is_link_local(msg->addr) && max_len < sizeof(struct babel_tlv_ihu) + 8)
return 0;
@ -576,12 +615,24 @@ babel_write_ihu(struct babel_tlv *hdr, union babel_msg *m,
if (!ipa_is_link_local(msg->addr))
{
tlv->ae = BABEL_AE_WILDCARD;
return sizeof(struct babel_tlv_ihu);
goto out;
}
put_ip6_ll(&tlv->addr, msg->addr);
tlv->ae = BABEL_AE_IP6_LL;
hdr->length += 8;
return sizeof(struct babel_tlv_ihu) + 8;
len += 8;
out:
if (msg->tstamp)
{
int l = babel_write_timestamp(hdr, msg->tstamp, msg->tstamp_rcvd, max_len);
if (l < 0)
return 0;
len += l;
}
return len;
}
static int
@ -1249,6 +1300,66 @@ babel_write_source_prefix(struct babel_tlv *hdr, net_addr *n, uint max_len)
return len;
}
static int
babel_read_timestamp(struct babel_tlv *hdr, union babel_msg *msg,
struct babel_parse_state *state)
{
struct babel_subtlv_timestamp *tlv = (void *) hdr;
switch (msg->type)
{
case BABEL_TLV_HELLO:
if (tlv->length < 4)
return PARSE_ERROR;
msg->hello.tstamp = get_u32(&tlv->tstamp);
msg->hello.pkt_received = state->received_time;
state->hello_tstamp_seen = 1;
break;
case BABEL_TLV_IHU:
if (tlv->length < 8)
return PARSE_ERROR;
/* RTT calculation relies on a Hello always being present with an IHU */
if (!state->hello_tstamp_seen)
break;
msg->ihu.tstamp = get_u32(&tlv->tstamp);
msg->ihu.tstamp_rcvd = get_u32(&tlv->tstamp_rcvd);
msg->ihu.pkt_received = state->received_time;
break;
default:
return PARSE_ERROR;
}
return PARSE_SUCCESS;
}
static int
babel_write_timestamp(struct babel_tlv *hdr, u32 tstamp, u32 tstamp_rcvd, uint max_len)
{
struct babel_subtlv_timestamp *tlv = (void *) NEXT_TLV(hdr);
uint len = sizeof(*tlv);
if (hdr->type == BABEL_TLV_HELLO)
len -= 4;
if (len > max_len)
return -1;
TLV_HDR(tlv, BABEL_SUBTLV_TIMESTAMP, len);
hdr->length += len;
put_u32(&tlv->tstamp, tstamp);
if (hdr->type == BABEL_TLV_IHU)
put_u32(&tlv->tstamp_rcvd, tstamp_rcvd);
return len;
}
static inline int
babel_read_subtlvs(struct babel_tlv *hdr,
union babel_msg *msg,
@ -1518,6 +1629,13 @@ babel_process_packet(struct babel_iface *ifa,
.saddr = saddr,
.next_hop_ip6 = saddr,
.sadr_enabled = babel_sadr_enabled(p),
/*
* The core updates current_time() after returning from poll(), so this is
* actually the time the packet was received, even though there may have
* been a bit of delay before we got to process it
*/
.received_time = current_time(),
};
if ((pkt->magic != BABEL_MAGIC) || (pkt->version != BABEL_VERSION))

View File

@ -635,7 +635,7 @@ bfd_reconfigure_iface(struct bfd_proto *p UNUSED, struct bfd_iface *ifa, struct
*/
static void
bfd_request_notify(struct bfd_request *req, u8 state, u8 diag)
bfd_request_notify(struct bfd_request *req, u8 state, u8 remote, u8 diag)
{
u8 old_state = req->state;
@ -645,7 +645,7 @@ bfd_request_notify(struct bfd_request *req, u8 state, u8 diag)
req->state = state;
req->diag = diag;
req->old_state = old_state;
req->down = (old_state == BFD_STATE_UP) && (state == BFD_STATE_DOWN);
req->down = (old_state == BFD_STATE_UP) && (state == BFD_STATE_DOWN) && (remote != BFD_STATE_ADMIN_DOWN);
if (req->hook)
{
@ -780,7 +780,7 @@ bfd_pickup_requests(void *_data UNUSED)
node *n;
WALK_LIST(n, tmp_list)
bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), BFD_STATE_ADMIN_DOWN, 0);
bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), BFD_STATE_ADMIN_DOWN, BFD_STATE_ADMIN_DOWN, 0);
BFD_LOCK;
add_tail_list(&bfd_global.wait_list, &tmp_list);
@ -1026,7 +1026,7 @@ bfd_notify_hook(void *data)
struct bfd_proto *p = data;
struct bfd_session *s;
list tmp_list;
u8 state, diag;
u8 loc_state, rem_state, diag;
node *n, *nn;
bfd_lock_sessions(p);
@ -1039,12 +1039,13 @@ bfd_notify_hook(void *data)
{
bfd_lock_sessions(p);
rem_node(&s->n);
state = s->loc_state;
loc_state = s->loc_state;
rem_state = s->rem_state;
diag = s->loc_diag;
bfd_unlock_sessions(p);
WALK_LIST_DELSAFE(n, nn, s->request_list)
bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), state, diag);
bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), loc_state, rem_state, diag);
/* Remove the session if all requests were removed in notify hooks */
if (EMPTY_LIST(s->request_list))

View File

@ -13,7 +13,7 @@
#include "nest/cli.h"
#include "nest/iface.h"
#include "nest/protocol.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/password.h"
#include "conf/conf.h"
#include "lib/hash.h"

View File

@ -97,7 +97,7 @@ bfd_iface_finish:
BFD_IFACE->passwords = get_passwords();
if (!BFD_IFACE->auth_type != !BFD_IFACE->passwords)
log(L_WARN "Authentication and password options should be used together");
cf_warn("Authentication and password options should be used together");
if (BFD_IFACE->passwords)
{

View File

@ -15,7 +15,7 @@
#include "nest/bird.h"
#include "nest/iface.h"
#include "nest/protocol.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "lib/attrs.h"
#include "conf/conf.h"
#include "lib/resource.h"
@ -1223,8 +1223,8 @@ bgp_register_attrs(void)
if (!bgp_attr_table[i].name)
bgp_attr_table[i] = (union bgp_attr_desc) {
.name = mb_sprintf(&root_pool, "bgp_unknown_0x%02x", i),
.type = T_OPAQUE,
.flags = BAF_OPTIONAL,
.type = T_BYTESTRING,
.flags = BAF_OPTIONAL | BAF_TRANSITIVE,
.readonly = 1,
.export = bgp_export_unknown,
.encode = bgp_encode_raw,
@ -1236,6 +1236,13 @@ bgp_register_attrs(void)
}
}
struct ea_class *
bgp_find_ea_class_by_id(uint id)
{
return (id < ARRAY_SIZE(bgp_attr_table)) ? &bgp_attr_table[id].class : NULL;
}
/*
* Attribute export
*/
@ -1657,6 +1664,9 @@ bgp_defer_bucket(struct bgp_channel *bc, struct bgp_bucket *b)
void
bgp_withdraw_bucket(struct bgp_channel *bc, struct bgp_bucket *b)
{
if (b->bmp)
return;
struct bgp_proto *p = (void *) bc->c.proto;
struct bgp_pending_tx *c = bc->ptx;
struct bgp_bucket *wb = bgp_get_withdraw_bucket(c);
@ -1807,6 +1817,10 @@ bgp_free_prefix(struct bgp_pending_tx *c, struct bgp_prefix *px)
void
bgp_done_prefix(struct bgp_channel *c, struct bgp_prefix *px, struct bgp_bucket *buck)
{
/* BMP hack */
if (buck->bmp)
return;
/* Cleanup: We're called from bucket senders. */
ASSERT_DIE(px->cur == buck);
rem_node(&px->buck_node_xx);

View File

@ -86,7 +86,6 @@
* RFC 5065 - AS confederations for BGP
* RFC 5082 - Generalized TTL Security Mechanism
* RFC 5492 - Capabilities Advertisement with BGP
* RFC 5549 - Advertising IPv4 NLRI with an IPv6 Next Hop
* RFC 5575 - Dissemination of Flow Specification Rules
* RFC 5668 - 4-Octet AS Specific BGP Extended Community
* RFC 6286 - AS-Wide Unique BGP Identifier
@ -101,6 +100,7 @@
* RFC 8203 - BGP Administrative Shutdown Communication
* RFC 8212 - Default EBGP Route Propagation Behavior without Policies
* RFC 8654 - Extended Message Support for BGP
* RFC 8950 - Advertising IPv4 NLRI with an IPv6 Next Hop
* RFC 9072 - Extended Optional Parameters Length for BGP OPEN Message
* RFC 9117 - Revised Validation Procedure for BGP Flow Specifications
* RFC 9234 - Route Leak Prevention and Detection Using Roles
@ -115,7 +115,7 @@
#include "nest/bird.h"
#include "nest/iface.h"
#include "nest/protocol.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/cli.h"
#include "nest/locks.h"
#include "conf/conf.h"
@ -125,6 +125,9 @@
#include "lib/string.h"
#include "bgp.h"
#ifdef CONFIG_BMP
#include "proto/bmp/bmp.h"
#endif
static void bgp_listen_create(void *);
@ -476,10 +479,20 @@ bgp_close_conn(struct bgp_conn *conn)
sk_close(conn->sk);
conn->sk = NULL;
mb_free(conn->local_open_msg);
conn->local_open_msg = NULL;
mb_free(conn->remote_open_msg);
conn->remote_open_msg = NULL;
conn->local_open_length = 0;
conn->remote_open_length = 0;
mb_free(conn->local_caps);
conn->local_caps = NULL;
mb_free(conn->remote_caps);
conn->remote_caps = NULL;
conn->notify_data = NULL;
conn->notify_size = 0;
}
@ -593,8 +606,8 @@ bgp_spawn(struct bgp_proto *pp, ip_addr remote_ip)
/* This is hack, we would like to share config, but we need to copy it now */
new_config = config;
cfg_mem = config->mem;
conf_this_scope = config->root_scope;
sym = cf_default_name(fmt, &(pp->dynamic_name_counter));
config->current_scope = config->root_scope;
sym = cf_default_name(config, fmt, &(pp->dynamic_name_counter));
proto_clone_config(sym, pp->p.cf);
new_config = NULL;
cfg_mem = NULL;
@ -785,10 +798,15 @@ bgp_conn_enter_established_state(struct bgp_conn *conn)
bgp_conn_set_state(conn, BS_ESTABLISHED);
proto_notify_state(&p->p, PS_UP);
#ifdef CONFIG_BMP
bmp_peer_up(p, conn->local_open_msg, conn->local_open_length,
conn->remote_open_msg, conn->remote_open_length);
#endif
}
static void
bgp_conn_leave_established_state(struct bgp_proto *p)
bgp_conn_leave_established_state(struct bgp_conn *conn, struct bgp_proto *p)
{
BGP_TRACE(D_EVENTS, "BGP session closed");
p->last_established = current_time();
@ -796,6 +814,12 @@ bgp_conn_leave_established_state(struct bgp_proto *p)
if (p->p.proto_state == PS_UP)
bgp_stop(p, 0, NULL, 0);
#ifdef CONFIG_BMP
bmp_peer_down(p, p->last_error_class,
conn->notify_code, conn->notify_subcode,
conn->notify_data, conn->notify_size);
#endif
}
void
@ -812,7 +836,7 @@ bgp_conn_enter_close_state(struct bgp_conn *conn)
bgp_start_timer(p, conn->hold_timer, 10);
if (os == BS_ESTABLISHED)
bgp_conn_leave_established_state(p);
bgp_conn_leave_established_state(conn, p);
}
void
@ -826,7 +850,7 @@ bgp_conn_enter_idle_state(struct bgp_conn *conn)
proto_send_event(&p->p, p->event);
if (os == BS_ESTABLISHED)
bgp_conn_leave_established_state(p);
bgp_conn_leave_established_state(conn, p);
}
/**
@ -1769,7 +1793,7 @@ bgp_start(struct proto *P)
* so that we are the only instance attempting to talk with that neighbor.
*/
struct object_lock *lock;
lock = p->lock = olock_new(P->pool_fragile);
lock = p->lock = olock_new(P->pool_inloop);
lock->addr = p->remote_ip;
lock->port = p->cf->remote_port;
lock->iface = p->cf->iface;
@ -2453,12 +2477,13 @@ bgp_error(struct bgp_conn *c, uint code, uint subcode, byte *data, int len)
bgp_log_error(p, BE_BGP_TX, "Error", code, subcode, data, ABS(len));
bgp_store_error(p, c, BE_BGP_TX, (code << 16) | subcode);
bgp_conn_enter_close_state(c);
c->notify_code = code;
c->notify_subcode = subcode;
c->notify_data = data;
c->notify_size = (len > 0) ? len : 0;
bgp_conn_enter_close_state(c);
bgp_schedule_packet(c, NULL, PKT_NOTIFICATION);
if (code != 6)
@ -2845,7 +2870,7 @@ bgp_show_proto_info(struct proto *P)
}
}
struct channel_class channel_bgp = {
const struct channel_class channel_bgp = {
.channel_size = sizeof(struct bgp_channel),
.config_size = sizeof(struct bgp_channel_config),
.init = bgp_channel_init,

View File

@ -14,7 +14,7 @@
#include <stdint.h>
#include <setjmp.h>
#include "nest/bird.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "nest/bfd.h"
//#include "lib/lists.h"
#include "lib/hash.h"
@ -96,6 +96,7 @@ struct bgp_config {
u32 default_med; /* Default value for MULTI_EXIT_DISC attribute */
int capabilities; /* Enable capability handshake [RFC 5492] */
int enable_refresh; /* Enable local support for route refresh [RFC 2918] */
int enable_enhanced_refresh; /* Enable local support for enhanced route refresh [RFC 7313] */
int enable_as4; /* Enable local support for 4B AS numbers [RFC 6793] */
int enable_extended_messages; /* Enable local support for extended messages [RFC 8654] */
int enable_hostname; /* Enable local support for hostname [draft] */
@ -230,7 +231,7 @@ struct bgp_af_caps {
u8 llgr_able; /* Long-lived GR, RFC draft */
u32 llgr_time; /* Long-lived GR stale time */
u8 llgr_flags; /* Long-lived GR per-AF flags */
u8 ext_next_hop; /* Extended IPv6 next hop, RFC 5549 */
u8 ext_next_hop; /* Extended IPv6 next hop, RFC 8950 */
u8 add_path; /* Multiple paths support, RFC 7911 */
};
@ -285,6 +286,11 @@ struct bgp_conn {
u8 ext_messages; /* Session uses extended message length */
u32 received_as; /* ASN received in OPEN message */
byte *local_open_msg; /* Saved OPEN messages (no header) */
byte *remote_open_msg;
uint local_open_length;
uint remote_open_length;
struct bgp_caps *local_caps;
struct bgp_caps *remote_caps;
timer *connect_timer;
@ -419,7 +425,8 @@ struct bgp_bucket {
struct bgp_bucket *next; /* Node in bucket hash table */
list prefixes; /* Prefixes to send in this bucket (struct bgp_prefix) */
u32 hash; /* Hash over extended attributes */
u32 px_uc; /* How many prefixes are linking this bucket */
u32 px_uc:31; /* How many prefixes are linking this bucket */
u32 bmp:1; /* Temporary bucket for BMP encoding */
ea_list eattrs[0]; /* Per-bucket extended attributes */
};
@ -509,6 +516,7 @@ struct bgp_parse_state {
#define BGP_PORT 179
#define BGP_VERSION 4
#define BGP_HEADER_LENGTH 19
#define BGP_HDR_MARKER_LENGTH 16
#define BGP_MAX_MESSAGE_LENGTH 4096
#define BGP_MAX_EXT_MSG_LENGTH 65535
#define BGP_RX_BUFFER_SIZE 4096
@ -519,6 +527,13 @@ struct bgp_parse_state {
#define BGP_CF_WALK_CHANNELS(P,C) WALK_LIST(C, P->c.channels) if (C->c.class == &channel_bgp)
#define BGP_WALK_CHANNELS(P,C) WALK_LIST(C, P->p.channels) if (C->c.class == &channel_bgp)
#define BGP_MSG_HDR_MARKER_SIZE 16
#define BGP_MSG_HDR_MARKER_POS 0
#define BGP_MSG_HDR_LENGTH_SIZE 2
#define BGP_MSG_HDR_LENGTH_POS BGP_MSG_HDR_MARKER_SIZE
#define BGP_MSG_HDR_TYPE_SIZE 1
#define BGP_MSG_HDR_TYPE_POS (BGP_MSG_HDR_MARKER_SIZE + BGP_MSG_HDR_LENGTH_SIZE)
static inline int bgp_channel_is_ipv4(struct bgp_channel *c)
{ return BGP_AFI(c->afi) == BGP_AFI_IPV4; }
@ -632,6 +647,8 @@ static inline struct bgp_proto *bgp_rte_proto(const rte *rte)
SKIP_BACK(struct bgp_proto, p.sources, rte->src->owner) : NULL;
}
byte * bgp_bmp_encode_rte(struct bgp_channel *c, byte *buf, const net_addr *n, const struct rte *new, const struct rte_src *src);
#define BGP_AIGP_METRIC 1
#define BGP_AIGP_MAX U64(0xffffffffffffffff)
@ -646,6 +663,7 @@ bgp_total_aigp_metric(const rte *e)
}
void bgp_register_attrs(void);
struct ea_class *bgp_find_ea_class_by_id(uint id);
/* packets.c */
@ -663,6 +681,7 @@ const char * bgp_error_dsc(unsigned code, unsigned subcode);
void bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsigned subcode, byte *data, unsigned len);
void bgp_update_next_hop(struct bgp_export_state *s, eattr *a, ea_list **to);
byte *bgp_create_end_mark_(struct bgp_channel *c, byte *buf);
/* Packet types */

View File

@ -23,7 +23,7 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE,
BGP_LOCAL_PREF, BGP_MED,
SOURCE, ADDRESS, PASSWORD, RR, RS, CLIENT, CLUSTER, ID, AS4, ADVERTISE,
IPV4, CAPABILITIES, LIMIT, PASSIVE, PREFER, OLDER, MISSING, LLADDR,
DROP, IGNORE, ROUTE, REFRESH, INTERPRET, COMMUNITIES,
DROP, IGNORE, ENHANCED, ROUTE, REFRESH, INTERPRET, COMMUNITIES,
IGP, TABLE, GATEWAY, DIRECT, RECURSIVE, MED, TTL,
SECURITY, DETERMINISTIC, SECONDARY, ALLOW, BFD, ADD, PATHS, RX, TX,
GRACEFUL, RESTART, AWARE, CHECK, LINK, PORT, EXTENDED, MESSAGES, SETKEY,
@ -44,10 +44,6 @@ CF_KEYWORDS(CEASE, PREFIX, LIMIT, HIT, ADMINISTRATIVE, SHUTDOWN, RESET, PEER,
CF_GRAMMAR
/* Workaround for collisions between keywords and symbols */
toksym: ROLE | PEER | PROVIDER | CUSTOMER | RS_SERVER | RS_CLIENT ;
toksym: BGP_MED | BGP_LOCAL_PREF | SOURCE ;
proto: bgp_proto '}' ;
bgp_proto_start: proto_start BGP {
@ -66,6 +62,7 @@ bgp_proto_start: proto_start BGP {
BGP_CFG->error_delay_time_min = 60;
BGP_CFG->error_delay_time_max = 300;
BGP_CFG->enable_refresh = 1;
BGP_CFG->enable_enhanced_refresh = 1;
BGP_CFG->enable_as4 = 1;
BGP_CFG->enable_hostname = 0;
BGP_CFG->capabilities = 2;
@ -179,7 +176,7 @@ bgp_proto:
| bgp_proto DEFAULT BGP_MED expr ';' { BGP_CFG->default_med = $4; }
| bgp_proto DEFAULT BGP_LOCAL_PREF expr ';' { BGP_CFG->default_local_pref = $4; }
| bgp_proto SOURCE ADDRESS ipa ';' { BGP_CFG->local_ip = $4; }
| bgp_proto START DELAY TIME expr ';' { BGP_CFG->connect_delay_time = $5; log(L_WARN "%s: Start delay time option is deprecated, use connect delay time", this_proto->name); }
| bgp_proto START DELAY TIME expr ';' { BGP_CFG->connect_delay_time = $5; cf_warn("%s: Start delay time option is deprecated, use connect delay time", this_proto->name); }
| bgp_proto CONNECT DELAY TIME expr ';' { BGP_CFG->connect_delay_time = $5; }
| bgp_proto CONNECT RETRY TIME expr ';' { BGP_CFG->connect_retry_time = $5; }
| bgp_proto KEEPALIVE TIME expr ';' { BGP_CFG->keepalive_time = $4; if (($4<1) || ($4>65535)) cf_error("Keepalive time must be in range 1-65535"); }
@ -189,6 +186,7 @@ bgp_proto:
| bgp_proto DISABLE AFTER ERROR bool ';' { BGP_CFG->disable_after_error = $5; }
| bgp_proto DISABLE AFTER CEASE bgp_cease_mask ';' { BGP_CFG->disable_after_cease = $5; }
| bgp_proto ENABLE ROUTE REFRESH bool ';' { BGP_CFG->enable_refresh = $5; }
| bgp_proto ENABLE ENHANCED ROUTE REFRESH bool ';' { BGP_CFG->enable_enhanced_refresh = $6; }
| bgp_proto ENABLE AS4 bool ';' { BGP_CFG->enable_as4 = $4; }
| bgp_proto ENABLE EXTENDED MESSAGES bool ';' { BGP_CFG->enable_extended_messages = $5; }
| bgp_proto ADVERTISE HOSTNAME bool ';' { BGP_CFG->enable_hostname = $4; }
@ -272,7 +270,7 @@ bgp_channel_item:
| NEXT HOP KEEP bgp_nh { BGP_CC->next_hop_keep = $4; }
| NEXT HOP PREFER GLOBAL { BGP_CC->next_hop_prefer = NHP_GLOBAL; }
| MANDATORY bool { BGP_CC->mandatory = $2; }
| MISSING LLADDR bgp_lladdr { log(L_WARN "%s.%s: Missing lladdr option is deprecated and ignored, remove it", this_proto->name, this_channel->name); }
| MISSING LLADDR bgp_lladdr { cf_warn("%s.%s: Missing lladdr option is deprecated and ignored, remove it", this_proto->name, this_channel->name); }
| GATEWAY DIRECT { BGP_CC->gw_mode = GW_DIRECT; }
| GATEWAY RECURSIVE { BGP_CC->gw_mode = GW_RECURSIVE; }
| SECONDARY bool { BGP_CC->secondary = $2; }
@ -339,6 +337,19 @@ bgp_channel_end:
bgp_proto_channel: bgp_channel_start bgp_channel_opt_list bgp_channel_end;
custom_attr: ATTRIBUTE BGP NUM type symbol ';' {
if ($3 > 255 || $3 < 1)
cf_error("Invalid attribute number. (Given %i, must be 1-255.)", $3);
struct ea_class *ac = bgp_find_ea_class_by_id($3);
ASSERT_DIE(ac);
if ($4 != ac->type)
cf_error("Attribute %d type must be %s, not %s.", $3, f_type_name(ac->type), f_type_name($4));
ea_ref_class(new_config->pool, ac);
cf_define_symbol(new_config, $5, SYM_ATTRIBUTE, attribute, ac);
};
CF_ENUM(T_ENUM_BGP_ORIGIN, ORIGIN_, IGP, EGP, INCOMPLETE)
CF_CODE

View File

@ -15,7 +15,7 @@
#include "nest/bird.h"
#include "nest/iface.h"
#include "nest/protocol.h"
#include "nest/rt.h"
#include "nest/route.h"
#include "lib/attrs.h"
#include "proto/mrt/mrt.h"
#include "conf/conf.h"
@ -26,6 +26,9 @@
#include "nest/cli.h"
#include "bgp.h"
#ifdef CONFIG_BMP
#include "proto/bmp/bmp.h"
#endif
#define BGP_RR_REQUEST 0
@ -213,6 +216,13 @@ bgp_af_caps_cmp(const void *X, const void *Y)
return (x->afi < y->afi) ? -1 : (x->afi > y->afi) ? 1 : 0;
}
struct bgp_caps *
bgp_alloc_capabilities(struct bgp_proto *p, int n)
{
struct bgp_caps *caps = mb_allocz(p->p.pool, sizeof(struct bgp_caps) + n * sizeof(struct bgp_af_caps));
caps->role = BGP_ROLE_UNDEFINED;
return caps;
}
void
bgp_prepare_capabilities(struct bgp_conn *conn)
@ -225,19 +235,19 @@ bgp_prepare_capabilities(struct bgp_conn *conn)
if (!p->cf->capabilities)
{
/* Just prepare empty local_caps */
conn->local_caps = mb_allocz(p->p.pool, sizeof(struct bgp_caps));
conn->local_caps = bgp_alloc_capabilities(p, 0);
return;
}
/* Prepare bgp_caps structure */
int n = list_length(&p->p.channels);
caps = mb_allocz(p->p.pool, sizeof(struct bgp_caps) + n * sizeof(struct bgp_af_caps));
caps = bgp_alloc_capabilities(p, n);
conn->local_caps = caps;
caps->as4_support = p->cf->enable_as4;
caps->ext_messages = p->cf->enable_extended_messages;
caps->route_refresh = p->cf->enable_refresh;
caps->enhanced_refresh = p->cf->enable_refresh;
caps->enhanced_refresh = p->cf->enable_refresh && p->cf->enable_enhanced_refresh;
caps->role = p->cf->local_role;
if (caps->as4_support)
@ -462,10 +472,7 @@ bgp_read_capabilities(struct bgp_conn *conn, byte *pos, int len)
u32 af;
if (!conn->remote_caps)
{
caps = mb_allocz(p->p.pool, sizeof(struct bgp_caps) + sizeof(struct bgp_af_caps));
caps->role = BGP_ROLE_UNDEFINED;
}
caps = bgp_alloc_capabilities(p, 1);
else
{
caps = conn->remote_caps;
@ -501,7 +508,7 @@ bgp_read_capabilities(struct bgp_conn *conn, byte *pos, int len)
caps->route_refresh = 1;
break;
case 5: /* Extended next hop encoding capability, RFC 5549 */
case 5: /* Extended next hop encoding capability, RFC 8950 */
if (cl % 6)
goto err;
@ -761,7 +768,7 @@ bgp_read_options(struct bgp_conn *conn, byte *pos, uint len, uint rest)
/* Prepare empty caps if no capability option was announced */
if (!conn->remote_caps)
conn->remote_caps = mb_allocz(p->p.pool, sizeof(struct bgp_caps));
conn->remote_caps = bgp_alloc_capabilities(p, 0);
return 0;
@ -770,6 +777,14 @@ err:
return -1;
}
static byte *
bgp_copy_open(struct bgp_proto *p, const byte *pkt, uint len)
{
char *buf = mb_alloc(p->p.pool, len - BGP_HEADER_LENGTH);
memcpy(buf, pkt + BGP_HEADER_LENGTH, len - BGP_HEADER_LENGTH);
return buf;
}
static byte *
bgp_create_open(struct bgp_conn *conn, byte *buf)
{
@ -844,6 +859,9 @@ bgp_rx_open(struct bgp_conn *conn, byte *pkt, uint len)
id = get_u32(pkt+24);
BGP_TRACE(D_PACKETS, "Got OPEN(as=%d,hold=%d,id=%R)", asn, hold, id);
conn->remote_open_msg = bgp_copy_open(p, pkt, len);
conn->remote_open_length = len - BGP_HEADER_LENGTH;
if (bgp_read_options(conn, pkt+29, pkt[28], len-29) < 0)
return;
@ -1171,7 +1189,7 @@ bgp_use_gateway(struct bgp_export_state *s)
return NULL;
/* Do not use gateway from different VRF */
if (p->p.vrf && nhad->nh.iface && (p->p.vrf != nhad->nh.iface->master))
if (p->p.vrf && nhad->nh.iface && !if_in_vrf(nhad->nh.iface, p->p.vrf))
return 0;
/* Use it when exported to internal peers */
@ -1261,7 +1279,7 @@ bgp_encode_next_hop_ip(struct bgp_write_state *s, eattr *a, byte *buf, uint size
/*
* Both IPv4 and IPv6 next hops can be used (with ext_next_hop enabled). This
* is specified in RFC 5549 for IPv4 and in RFC 4798 for IPv6. The difference
* is specified in RFC 8950 for IPv4 and in RFC 4798 for IPv6. The difference
* is that IPv4 address is directly encoded with IPv4 NLRI, but as IPv4-mapped
* IPv6 address with IPv6 NLRI.
*/
@ -1336,7 +1354,7 @@ bgp_encode_next_hop_vpn(struct bgp_write_state *s, eattr *a, byte *buf, uint siz
/*
* Both IPv4 and IPv6 next hops can be used (with ext_next_hop enabled). This
* is specified in RFC 5549 for VPNv4 and in RFC 4659 for VPNv6. The difference
* is specified in RFC 8950 for VPNv4 and in RFC 4659 for VPNv6. The difference
* is that IPv4 address is directly encoded with VPNv4 NLRI, but as IPv4-mapped
* IPv6 address with VPNv6 NLRI.
*/
@ -2380,6 +2398,97 @@ bgp_create_mp_unreach(struct bgp_write_state *s, struct bgp_bucket *buck, byte *
return buf+11+len;
}
#ifdef CONFIG_BMP
static byte *
bgp_create_update_bmp(struct bgp_channel *c, byte *buf, struct bgp_bucket *buck, bool update)
{
struct bgp_proto *p = (void *) c->c.proto;
byte *end = buf + (BGP_MAX_EXT_MSG_LENGTH - BGP_HEADER_LENGTH);
byte *res = NULL;
/* FIXME: must be a bit shorter */
struct bgp_caps *peer = p->conn->remote_caps;
const struct bgp_af_caps *rem = bgp_find_af_caps(peer, c->afi);
struct bgp_write_state s = {
.proto = p,
.channel = c,
.pool = tmp_linpool,
.mp_reach = (c->afi != BGP_AF_IPV4) || (rem && rem->ext_next_hop),
.as4_session = 1,
.add_path = c->add_path_rx,
.mpls = c->desc->mpls,
};
if (!update)
{
res = !s.mp_reach ?
bgp_create_ip_unreach(&s, buck, buf, end):
bgp_create_mp_unreach(&s, buck, buf, end);
}
else
{
res = !s.mp_reach ?
bgp_create_ip_reach(&s, buck, buf, end):
bgp_create_mp_reach(&s, buck, buf, end);
}
return res;
}
static byte *
bgp_bmp_prepare_bgp_hdr(byte *buf, const u16 msg_size, const u8 msg_type)
{
memset(buf + BGP_MSG_HDR_MARKER_POS, 0xff, BGP_MSG_HDR_MARKER_SIZE);
put_u16(buf + BGP_MSG_HDR_LENGTH_POS, msg_size);
put_u8(buf + BGP_MSG_HDR_TYPE_POS, msg_type);
return buf + BGP_MSG_HDR_TYPE_POS + BGP_MSG_HDR_TYPE_SIZE;
}
byte *
bgp_bmp_encode_rte(struct bgp_channel *c, byte *buf, const net_addr *n,
const struct rte *new, const struct rte_src *src)
{
// struct bgp_proto *p = (void *) c->c.proto;
byte *pkt = buf + BGP_HEADER_LENGTH;
ea_list *attrs = new ? new->attrs : NULL;
uint ea_size = new ? (sizeof(ea_list) + attrs->count * sizeof(eattr)) : 0;
uint bucket_size = sizeof(struct bgp_bucket) + ea_size;
uint prefix_size = sizeof(struct bgp_prefix) + n->length;
struct lp_state *tmpp = lp_save(tmp_linpool);
/* Temporary bucket */
struct bgp_bucket *b = tmp_allocz(bucket_size);
b->bmp = 1;
init_list(&b->prefixes);
if (attrs)
memcpy(b->eattrs, attrs, ea_size);
/* Temporary prefix */
struct bgp_prefix *px = tmp_allocz(prefix_size);
px->path_id = (u32) src->private_id;
net_copy(px->net, n);
add_tail(&b->prefixes, &px->buck_node_xx);
byte *end = bgp_create_update_bmp(c, pkt, b, !!new);
if (end)
bgp_bmp_prepare_bgp_hdr(buf, end - buf, PKT_UPDATE);
lp_restore(tmp_linpool, tmpp);
return end;
}
#endif /* CONFIG_BMP */
static byte *
bgp_create_update(struct bgp_channel *c, byte *buf)
{
@ -2473,6 +2582,14 @@ bgp_create_mp_end_mark(struct bgp_channel *c, byte *buf)
return buf+10;
}
byte *
bgp_create_end_mark_(struct bgp_channel *c, byte *buf)
{
return (c->afi == BGP_AF_IPV4) ?
bgp_create_ip_end_mark(c, buf):
bgp_create_mp_end_mark(c, buf);
}
static byte *
bgp_create_end_mark(struct bgp_channel *c, byte *buf)
{
@ -2481,9 +2598,7 @@ bgp_create_end_mark(struct bgp_channel *c, byte *buf)
BGP_TRACE(D_PACKETS, "Sending END-OF-RIB");
p->stats.tx_updates++;
return (c->afi == BGP_AF_IPV4) ?
bgp_create_ip_end_mark(c, buf):
bgp_create_mp_end_mark(c, buf);
return bgp_create_end_mark_(c, buf);
}
static inline void
@ -2624,7 +2739,6 @@ bgp_rx_update(struct bgp_conn *conn, byte *pkt, uint len)
s.ip_reach_len = len - pos;
s.ip_reach_nlri = pkt + pos;
if (s.attr_len)
ea = bgp_decode_attrs(&s, s.attrs, s.attr_len);
else
@ -2863,7 +2977,7 @@ bgp_send(struct bgp_conn *conn, uint type, uint len)
conn->bgp->stats.tx_messages++;
conn->bgp->stats.tx_bytes += len;
memset(buf, 0xff, 16); /* Marker */
memset(buf, 0xff, BGP_HDR_MARKER_LENGTH);
put_u16(buf+16, len);
buf[18] = type;
@ -2911,6 +3025,10 @@ bgp_fire_tx(struct bgp_conn *conn)
{
conn->packets_to_send &= ~(1 << PKT_OPEN);
end = bgp_create_open(conn, pkt);
conn->local_open_msg = bgp_copy_open(p, buf, end - buf);
conn->local_open_length = end - buf - BGP_HEADER_LENGTH;
return bgp_send(conn, PKT_OPEN, end - buf);
}
else if (s & (1 << PKT_KEEPALIVE))
@ -3197,6 +3315,11 @@ bgp_rx_notification(struct bgp_conn *conn, byte *pkt, uint len)
bgp_log_error(p, BE_BGP_RX, "Received", code, subcode, pkt+21, len-21);
bgp_store_error(p, conn, BE_BGP_RX, (code << 16) | subcode);
conn->notify_code = code;
conn->notify_subcode = subcode;
conn->notify_data = pkt+21;
conn->notify_size = len-21;
bgp_conn_enter_close_state(conn);
bgp_schedule_packet(conn, NULL, PKT_SCHEDULE_CLOSE);

1
proto/bmp/Doc Normal file
View File

@ -0,0 +1 @@
S bmp.c

2
proto/bmp/LICENSE Normal file
View File

@ -0,0 +1,2 @@
The patch is provided under the terms of the GNU General Public License, either
version 2, or any later version.

6
proto/bmp/Makefile Normal file
View File

@ -0,0 +1,6 @@
src := bmp.c buffer.c map.c
obj := $(src-o-files)
$(all-daemon)
$(cf-local)
tests_objs := $(tests_objs) $(src-o-files)

6
proto/bmp/README.txt Normal file
View File

@ -0,0 +1,6 @@
ABOUT
This package |proto/bmp/*| provide implementation of BGP Monitoring Protocol (BMP).
It has been started by Akamai Technologies, Inc. as a pilot program for support BMP in BIRD.
It provides only basic features of BMP specification which are needed by Akamai evaluation of
feasible BMP protocol.
Content of this package has been provided as a patch for BIRD release v2.0.7.

1367
proto/bmp/bmp.c Normal file

File diff suppressed because it is too large Load Diff

129
proto/bmp/bmp.h Normal file
View File

@ -0,0 +1,129 @@
/*
* BIRD -- The BGP Monitoring Protocol (BMP)
*
* (c) 2020 Akamai Technologies, Inc. (Pawel Maslanka, pmaslank@akamai.com)
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#ifndef _BIRD_BMP_H_
#define _BIRD_BMP_H_
#include "nest/bird.h"
#include "nest/protocol.h"
#include "lib/lists.h"
#include "nest/route.h"
#include "lib/event.h"
#include "lib/hash.h"
#include "lib/socket.h"
#include "proto/bmp/map.h"
#include <stdbool.h>
// Max length of MIB-II description object
#define MIB_II_STR_LEN 255
// The following fields of this structure controls whether there will be put
// specific routes into Route Monitoring message and send to BMP collector
struct monitoring_rib {
bool in_pre_policy; // Monitoring pre-policy Adj-Rib-In
bool in_post_policy; // Monitoring post-policy Adj-Rib-In
bool local; // Monitoring Local Rib
};
struct bmp_config {
struct proto_config c;
const char *sys_descr; // sysDescr MIB-II [RFC1213] object
const char *sys_name; // sysName MIB-II [RFC1213] object
ip_addr local_addr; // Local IP address
ip_addr station_ip; // Monitoring station address
u16 station_port; // Monitoring station TCP port
bool monitoring_rib_in_pre_policy; // Route monitoring pre-policy Adj-Rib-In
bool monitoring_rib_in_post_policy; // Route monitoring post-policy Adj-Rib-In
};
/* Forward declarations */
struct bgp_proto;
struct bmp_proto;
struct bmp_proto {
struct proto p; // Parent proto
const struct bmp_config *cf; // Shortcut to BMP configuration
node bmp_node; // Node in bmp_proto_list
HASH(struct bmp_peer) peer_map;
HASH(struct bmp_stream) stream_map;
HASH(struct bmp_table) table_map;
sock *sk; // TCP connection
event *tx_ev; // TX event
event *update_ev; // Update event
char sys_descr[MIB_II_STR_LEN]; // sysDescr MIB-II [RFC1213] object
char sys_name[MIB_II_STR_LEN]; // sysName MIB-II [RFC1213] object
ip_addr local_addr; // Source local IP address
ip_addr station_ip; // Monitoring station IP address
u16 station_port; // Monitoring station TCP port
struct monitoring_rib monitoring_rib;
// Below fields are for internal use
// struct bmp_peer_map bgp_peers; // Stores 'bgp_proto' structure per BGP peer
pool *buffer_mpool; // Memory pool used for BMP buffer allocations
pool *map_mem_pool; // Memory pool used for BMP map allocations
pool *tx_mem_pool; // Memory pool used for packet allocations designated to BMP collector
pool *update_msg_mem_pool; // Memory pool used for BPG UPDATE MSG allocations
list tx_queue; // Stores queued packets going to be sent
timer *connect_retry_timer; // Timer for retrying connection to the BMP collector
list update_msg_queue; // Stores all composed BGP UPDATE MSGs
bool started; // Flag that stores running status of BMP instance
int sock_err; // Last socket error code
};
struct bmp_peer {
struct bgp_proto *bgp;
struct bmp_peer *next;
list streams;
};
struct bmp_stream {
node n;
struct bgp_proto *bgp;
u32 key;
bool sync;
struct bmp_stream *next;
struct bmp_table *table;
struct bgp_channel *sender;
};
struct bmp_table {
struct rtable *table;
struct bmp_table *next;
struct channel *channel;
u32 uc;
};
#ifdef CONFIG_BMP
/**
* bmp_peer_up - send notification that BGP peer connection is established
*/
void
bmp_peer_up(struct bgp_proto *bgp,
const byte *tx_open_msg, uint tx_open_length,
const byte *rx_open_msg, uint rx_open_length);
/**
* bmp_peer_down - send notification that BGP peer connection is not in
* established state
*/
void
bmp_peer_down(const struct bgp_proto *bgp, int err_class, int code, int subcode, const byte *data, int length);
#else /* BMP build disabled */
static inline void bmp_peer_up(struct bgp_proto *bgp UNUSED, const byte *tx_open_msg UNUSED, uint tx_open_length UNUSED, const byte *rx_open_msg UNUSED, uint rx_open_length UNUSED) { }
static inline void bmp_peer_down(const struct bgp_proto *bgp UNUSED, const int err_class UNUSED, int code UNUSED, int subcode UNUSED, const byte *data UNUSED, int length UNUSED) { }
#endif /* CONFIG_BMP */
#endif /* _BIRD_BMP_H_ */

65
proto/bmp/buffer.c Normal file
View File

@ -0,0 +1,65 @@
/*
* BIRD -- The BGP Monitoring Protocol (BMP)
*
* (c) 2020 Akamai Technologies, Inc. (Pawel Maslanka, pmaslank@akamai.com)
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#include "proto/bmp/buffer.h"
buffer
bmp_buffer_alloc(pool *ppool, const size_t n)
{
buffer buf;
buf.start = mb_alloc(ppool, n);
buf.pos = buf.start;
buf.end = buf.start + n;
return buf;
}
void
bmp_buffer_free(buffer *buf)
{
mb_free(buf->start);
buf->start = buf->pos = buf->end = NULL;
}
/**
* @brief bmp_buffer_grow
* @param buf - buffer to grow
* @param n - required amount of available space
* Resize buffer in a way that there is at least @n bytes of available space.
*/
static void
bmp_buffer_grow(buffer *buf, const size_t n)
{
size_t pos = bmp_buffer_pos(buf);
size_t size = bmp_buffer_size(buf);
size_t req = pos + n;
while (size < req)
size = size * 3 / 2;
buf->start = mb_realloc(buf->start, size);
buf->pos = buf->start + pos;
buf->end = buf->start + size;
}
void
bmp_buffer_need(buffer *buf, const size_t n)
{
if (bmp_buffer_avail(buf) < n)
bmp_buffer_grow(buf, n);
}
void
bmp_put_data(buffer *buf, const void *src, const size_t n)
{
if (!n)
return;
bmp_buffer_need(buf, n);
memcpy(buf->pos, src, n);
buf->pos += n;
}

77
proto/bmp/buffer.h Normal file
View File

@ -0,0 +1,77 @@
/*
* BIRD -- The BGP Monitoring Protocol (BMP)
*
* (c) 2020 Akamai Technologies, Inc. (Pawel Maslanka, pmaslank@akamai.com)
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#ifndef _BIRD_BMP_BUFFER_H_
#define _BIRD_BMP_BUFFER_H_
#include "proto/bmp/bmp.h"
#include <stdlib.h>
#include "lib/resource.h"
buffer
bmp_buffer_alloc(pool *ppool, const size_t n);
void
bmp_buffer_free(buffer *buf);
static inline void
bmp_buffer_flush(buffer *buf)
{
buf->pos = buf->start;
}
static inline size_t
bmp_buffer_size(const buffer *buf)
{
return buf->end - buf->start;
}
static inline size_t
bmp_buffer_avail(const buffer *buf)
{
return buf->end - buf->pos;
}
static inline size_t
bmp_buffer_pos(const buffer *buf)
{
return buf->pos - buf->start;
}
static inline byte *
bmp_buffer_data(const buffer *buf)
{
return buf->start;
}
void
bmp_buffer_need(buffer *buf, const size_t n);
// Idea for following macros has been taken from |proto/mrt/mrt.c|
#define BMP_DEFINE_PUT_FUNC(S, T) \
static inline void \
bmp_put_##S(buffer *b, const T x) \
{ \
bmp_buffer_need(b, sizeof(T)); \
put_##S(b->pos, x); \
b->pos += sizeof(T); \
}
BMP_DEFINE_PUT_FUNC(u8, u8)
BMP_DEFINE_PUT_FUNC(u16, u16)
BMP_DEFINE_PUT_FUNC(u32, u32)
BMP_DEFINE_PUT_FUNC(u64, u64)
BMP_DEFINE_PUT_FUNC(ip4, ip4_addr)
BMP_DEFINE_PUT_FUNC(ip6, ip6_addr)
void
bmp_put_data(buffer *buf, const void *src, const size_t n);
#endif /* _BIRD_BMP_BUFFER_H_ */

78
proto/bmp/config.Y Normal file
View File

@ -0,0 +1,78 @@
/*
* BIRD -- The BGP Monitoring Protocol (BMP)
*
* (c) 2020 Akamai Technologies, Inc. (Pawel Maslanka, pmaslank@akamai.com)
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
CF_HDR
#include "proto/bmp/bmp.h"
CF_DEFINES
#define BMP_CFG ((struct bmp_config *) this_proto)
CF_DECLS
CF_KEYWORDS(BMP, DESCRIPTION, ENABLED, IN, IP, MONITORING, NAME, PORT,
PRE_POLICY, POST_POLICY, RIB, STATION, SYSTEM)
CF_GRAMMAR
proto: bmp_proto '}' ;
bmp_proto_start: proto_start BMP {
this_proto = proto_config_new(&proto_bmp, $1);
BMP_CFG->sys_descr = "Not defined";
BMP_CFG->sys_name = "Not defined";
}
;
bmp_station_address:
/* empty */
| bmp_station_address IP ipa {
if (ipa_zero($3))
cf_error("Invalid BMP monitoring station IP address");
BMP_CFG->station_ip = $3;
}
| bmp_station_address PORT expr {
if (($3 < 1) || ($3 > 65535))
cf_error("Invalid BMP monitoring station port number");
BMP_CFG->station_port = $3;
}
;
bmp_proto:
bmp_proto_start proto_name '{'
| bmp_proto proto_item ';'
| bmp_proto LOCAL ADDRESS ipa ';' {
BMP_CFG->local_addr = $4;
}
| bmp_proto STATION ADDRESS bmp_station_address ';'
| bmp_proto SYSTEM DESCRIPTION text ';' {
if (!$4 || (strlen($4) == 0))
cf_error("String is empty");
else if (strlen($4) > 255)
cf_error("Invalid string length");
BMP_CFG->sys_descr = $4;
}
| bmp_proto SYSTEM NAME text ';' {
if (!$4 || (strlen($4) == 0))
cf_error("String is empty");
else if (strlen($4) > 255)
cf_error("Invalid string length");
BMP_CFG->sys_name = $4;
}
| bmp_proto MONITORING RIB IN PRE_POLICY bool ';' {
BMP_CFG->monitoring_rib_in_pre_policy = $6;
}
| bmp_proto MONITORING RIB IN POST_POLICY bool ';' {
BMP_CFG->monitoring_rib_in_post_policy = $6;
}
;
CF_CODE
CF_END

119
proto/bmp/map.c Normal file
View File

@ -0,0 +1,119 @@
/*
* BIRD -- The BGP Monitoring Protocol (BMP)
*
* (c) 2020 Akamai Technologies, Inc. (Pawel Maslanka, pmaslank@akamai.com)
*
* Can be freely distributed and used under the terms of the GNU GPL.
*/
#include "proto/bmp/map.h"
/* Peer Index Table */
#define PEER_KEY(n) (n)->peer_as, (n)->peer_ip
#define PEER_NEXT(n) (n)->next
#define PEER_EQ(as1,ip1,as2,ip2) \
(as1) == (as2) && ipa_equal(ip1, ip2)
#define PEER_FN(as,ip) ipa_hash(ip)
#define PEER_REHASH bmp_peer_rehash
#define PEER_PARAMS /8, *2, 2, 2, 6, 20
HASH_DEFINE_REHASH_FN(PEER, struct bmp_peer_map_key)
#define PEER_INIT_ORDER 6
void
bmp_peer_map_init(struct bmp_peer_map *map, pool *mpool)
{
map->mpool = mpool;
HASH_INIT(map->peer_hash, map->mpool, PEER_INIT_ORDER);
}
struct bmp_peer_map_key
bmp_peer_map_key_create(const ip_addr peer_ip, const u32 peer_as)
{
struct bmp_peer_map_key key;
key.next = NULL;
key.peer_ip = peer_ip;
key.peer_as = peer_as;
return key;
}
void
bmp_peer_map_flush(struct bmp_peer_map *map)
{
struct bmp_peer_map_entry *entry;
HASH_WALK_DELSAFE(map->peer_hash, next, e)
{
entry = (struct bmp_peer_map_entry *) e;
mb_free(entry->data.buf);
HASH_DELETE(map->peer_hash, PEER, PEER_KEY(&entry->key));
mb_free(entry);
}
HASH_WALK_DELSAFE_END;
HASH_MAY_RESIZE_DOWN(map->peer_hash, PEER, map->mpool);
}
void
bmp_peer_map_free(struct bmp_peer_map *map)
{
bmp_peer_map_flush(map);
HASH_FREE(map->peer_hash);
}
void
bmp_peer_map_insert(struct bmp_peer_map *map, const struct bmp_peer_map_key key,
const byte *data, const size_t data_size)
{
struct bmp_peer_map_entry *entry
= (void *) HASH_FIND(map->peer_hash, PEER, PEER_KEY(&key));
if (entry)
{
mb_free(entry->data.buf);
entry->data.buf = mb_alloc(map->mpool, data_size);
memcpy(entry->data.buf, data, data_size);
entry->data.buf_size = data_size;
return;
}
entry = mb_alloc(map->mpool, sizeof (struct bmp_peer_map_entry));
entry->data.buf = mb_alloc(map->mpool, data_size);
memcpy(entry->data.buf, data, data_size);
entry->data.buf_size = data_size;
entry->key = key;
HASH_INSERT2(map->peer_hash, PEER, map->mpool, &entry->key);
}
void
bmp_peer_map_remove(struct bmp_peer_map *map, const struct bmp_peer_map_key key)
{
struct bmp_peer_map_entry *entry
= (void *) HASH_DELETE(map->peer_hash, PEER, PEER_KEY(&key));
if (!entry)
return;
mb_free(entry->data.buf);
mb_free(entry);
}
const struct bmp_peer_map_entry *
bmp_peer_map_get(struct bmp_peer_map *map, const struct bmp_peer_map_key key)
{
return (struct bmp_peer_map_entry *) HASH_FIND(map->peer_hash, PEER, PEER_KEY(&key));
}
void
bmp_peer_map_walk(const struct bmp_peer_map *map, bmp_peer_map_walk_action action, void *arg)
{
struct bmp_peer_map_entry *entry;
HASH_WALK_FILTER(map->peer_hash, next, e, _)
{
entry = (struct bmp_peer_map_entry *) e;
action(entry->key, entry->data.buf, entry->data.buf_size, arg);
}
HASH_WALK_FILTER_END;
}

Some files were not shown because too many files have changed in this diff Show More