From af582c4811175d9a27ed5d08a4f6d5eaa69ecec7 Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Sun, 18 Mar 2012 17:32:30 +0100 Subject: [PATCH] Route Origin Authorization basics. - ROA tables, which are used as a basic part for RPKI. - Commands for examining and modifying ROA tables. - Filter operators based on ROA tables consistent with RFC 6483. --- conf/cf-lex.l | 2 + conf/conf.c | 2 + conf/conf.h | 3 + conf/confbase.Y | 2 + doc/bird.sgml | 52 +++++- filter/config.Y | 5 +- filter/f-util.c | 18 ++ filter/filter.c | 41 ++++- filter/filter.h | 9 + filter/test.conf | 28 ++- lib/ip.h | 8 +- nest/Makefile | 2 +- nest/cmds.c | 2 + nest/config.Y | 90 +++++++++- nest/route.h | 80 +++++++++ nest/rt-roa.c | 440 +++++++++++++++++++++++++++++++++++++++++++++ sysdep/unix/main.c | 1 + 17 files changed, 773 insertions(+), 12 deletions(-) create mode 100644 nest/rt-roa.c diff --git a/conf/cf-lex.l b/conf/cf-lex.l index 408fa93a..8cd52c42 100644 --- a/conf/cf-lex.l +++ b/conf/cf-lex.l @@ -536,6 +536,8 @@ cf_symbol_class_name(struct symbol *sym) return "network address"; case SYM_TEMPLATE: return "protocol template"; + case SYM_ROA: + return "ROA table"; default: return "unknown type"; } diff --git a/conf/conf.c b/conf/conf.c index 4b605b36..13049be4 100644 --- a/conf/conf.c +++ b/conf/conf.c @@ -112,6 +112,7 @@ config_parse(struct config *c) sysdep_preconfig(c); protos_preconfig(c); rt_preconfig(c); + roa_preconfig(c); cf_parse(); protos_postconfig(c); if (EMPTY_LIST(c->protos)) @@ -210,6 +211,7 @@ config_do_commit(struct config *c, int type) force_restart |= global_commit(c, old_config); DBG("rt_commit\n"); rt_commit(c, old_config); + roa_commit(c, old_config); DBG("protos_commit\n"); protos_commit(c, old_config, force_restart, type); new_config = NULL; /* Just to be sure nobody uses that now */ diff --git a/conf/conf.h b/conf/conf.h index 8753bafe..b4ec3157 100644 --- a/conf/conf.h +++ b/conf/conf.h @@ -21,7 +21,9 @@ struct config { linpool *mem; /* Linear pool containing configuration data */ list protos; /* Configured protocol instances (struct proto_config) */ list tables; /* Configured routing tables (struct rtable_config) */ + list roa_tables; /* Configured ROA tables (struct roa_table_config) */ list logfiles; /* Configured log fils (sysdep) */ + int mrtdump_file; /* Configured MRTDump file (sysdep, fd in unix) */ char *syslog_name; /* Name used for syslog (NULL -> no syslog) */ struct rtable_config *master_rtc; /* Configuration of master routing table */ @@ -110,6 +112,7 @@ struct symbol { #define SYM_TABLE 5 #define SYM_IPA 6 #define SYM_TEMPLATE 7 +#define SYM_ROA 8 #define SYM_VARIABLE 0x100 /* 0x100-0x1ff are variable types */ diff --git a/conf/confbase.Y b/conf/confbase.Y index b9bc048e..dcb0719f 100644 --- a/conf/confbase.Y +++ b/conf/confbase.Y @@ -50,9 +50,11 @@ CF_DECLS struct f_path_mask *h; struct password_item *p; struct rt_show_data *ra; + struct roa_show_data *ro; struct sym_show_data *sd; struct lsadb_show_data *ld; struct iface *iface; + struct roa_table *rot; void *g; bird_clock_t time; struct prefix px; diff --git a/doc/bird.sgml b/doc/bird.sgml index aabb9857..7b163c7b 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -358,6 +358,22 @@ protocol rip { routing table is created implicitly, other routing tables have to be added by this command. + roa table [ { roa table options ... } ] + Create a new ROA (Route Origin Authorization) table. ROA + tables can be used to validate route origination of BGP + routes. A ROA table contains ROA entries, each consist of a + network prefix, a max prefix length and an AS number. A ROA + entry specifies prefixes which could be originated by that AS + number. ROA tables could be filled with data from RPKI (RFC + 6480) or from public databases like Whois. ROA tables are + examined by roa , which + can be used to populate the ROA table with static ROA + entries. The option may be used multiple times. Other entries + can be added dynamically by eval Evaluates given filter expression. It is used by us for testing of filters. @@ -570,7 +586,7 @@ This argument can be omitted if there exists only a single instance. show interfaces [summary] Show the list of interfaces. For each interface, print its type, state, MTU and addresses assigned. - show symbols [table|filter|function|protocol|template|] + show symbols [table|filter|function|protocol|template|roa|] Show the list of symbols defined in the configuration (names of protocols, routing tables etc.). show route [[for] @@ -599,6 +615,29 @@ This argument can be omitted if there exists only a single instance. number of networks, number of routes before and after filtering). If you use show xroa [] + Show contents of a ROA table (by default of the first one). + You can specify a for , you'll + get all entries relevant for route validation of the network + prefix; i.e., ROA entries whose prefixes cover the network + prefix. Or you can use in to get ROA entries + covered by the network prefix. You could also use add roa ] + Add a new ROA entry to a ROA table. Such entry is called + delete roa ] + Delete the specified ROA entry from a ROA table. Only dynamic + ROA entries (i.e., the ones added by flush roa [table ] + Remove all dynamic ROA entries from a ROA table. + configure [soft] [" Reload configuration from a given file. BIRD will smoothly switch itself to the new configuration, protocols are @@ -918,6 +957,17 @@ used on element and set of elements of the same type (returning true if element on two strings (returning true if first string matches a shell-like pattern stored in second string) or on IP and prefix (returning true if IP is within the range defined by that prefix), or on prefix and prefix (returning true if first prefix is more specific than second one) or on bgppath and bgpmask (returning true if the path matches the mask) or on number and bgppath (returning true if the number is in the path) or on pair/quad and clist (returning true if the pair/quad is element of the clist) or on clist and pair/quad set (returning true if there is an element of the clist that is also a member of the pair/quad set). +

There is one operator related to ROA infrastructure - +roa_check(, which checks current route (which +should be from BGP to have AS_PATH argument) in the specified ROA +table and returns ROA_UNKNOWN if there is no relevant ROA, ROA_VALID +if there is a matching ROA, or ROA_INVALID if there are some relevant +ROAs but none of them match. There is also an extended variant +roa_check(, which allows to +specify a prefix and an ASN as arguments. + Control structures diff --git a/filter/config.Y b/filter/config.Y index 7828bf8b..2e8b522e 100644 --- a/filter/config.Y +++ b/filter/config.Y @@ -259,6 +259,7 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN, DEFINED, ADD, DELETE, CONTAINS, RESET, PREPEND, FIRST, LAST, MATCH, + ROA_CHECK, EMPTY, FILTER, WHERE, EVAL) @@ -755,6 +756,9 @@ term: | DELETE '(' term ',' term ')' { $$ = f_new_inst(); $$->code = P('C','a'); $$->a1.p = $3; $$->a2.p = $5; $$->aux = 'd'; } | FILTER '(' term ',' term ')' { $$ = f_new_inst(); $$->code = P('C','a'); $$->a1.p = $3; $$->a2.p = $5; $$->aux = 'f'; } + | ROA_CHECK '(' SYM ')' { $$ = f_generate_roa_check($3, NULL, NULL); } + | ROA_CHECK '(' SYM ',' term ',' term ')' { $$ = f_generate_roa_check($3, $5, $7); } + /* | term '.' LEN { $$->code = P('P','l'); } */ /* function_call is inlined here */ @@ -801,7 +805,6 @@ print_list: /* EMPTY */ { $$ = NULL; } $$ = $1; } else $$ = $3; } - ; var_listn: term { diff --git a/filter/f-util.c b/filter/f-util.c index ec86a4b2..5908ac64 100644 --- a/filter/f-util.c +++ b/filter/f-util.c @@ -54,6 +54,24 @@ f_generate_complex(int operation, int operation_aux, struct f_inst *dyn, struct return set_dyn; } + +struct f_inst * +f_generate_roa_check(struct symbol *sym, struct f_inst *prefix, struct f_inst *asn) +{ + struct f_inst_roa_check *ret = cfg_allocz(sizeof(struct f_inst_roa_check)); + ret->i.code = P('R','C'); + ret->i.lineno = ifs->conf_lino; + ret->i.arg1 = prefix; + ret->i.arg2 = asn; + /* prefix == NULL <-> asn == NULL */ + + if ((sym->class != SYM_ROA) || ! sym->def) + cf_error("%s is not a ROA table", sym->name); + ret->rtc = sym->def; + + return &ret->i; +} + char * filter_name(struct filter *filter) { diff --git a/filter/filter.c b/filter/filter.c index a087c50b..acdcfd2b 100644 --- a/filter/filter.c +++ b/filter/filter.c @@ -248,7 +248,7 @@ val_simple_in_range(struct f_val v1, struct f_val v2) return ipa_in_net(v1.val.px.ip, v2.val.px.ip, v2.val.px.len); if ((v1.type == T_PREFIX) && (v2.type == T_PREFIX)) - return ipa_in_net(v1.val.px.ip, v2.val.px.ip, v2.val.px.len) && (v1.val.px.len >= v2.val.px.len); + return net_in_net(v1.val.px.ip, v1.val.px.len, v2.val.px.ip, v2.val.px.len); return CMP_ERROR; } @@ -1208,6 +1208,38 @@ interpret(struct f_inst *what) break; + case P('R','C'): /* ROA Check */ + if (what->arg1) + { + TWOARGS; + if ((v1.type != T_PREFIX) || (v2.type != T_INT)) + runtime("Invalid argument to roa_check()"); + + as = v2.val.i; + } + else + { + v1.val.px.ip = (*f_rte)->net->n.prefix; + v1.val.px.len = (*f_rte)->net->n.pxlen; + + /* We ignore temporary attributes, probably not a problem here */ + /* 0x02 is a value of BA_AS_PATH, we don't want to include BGP headers */ + eattr *e = ea_find((*f_rte)->attrs->eattrs, EA_CODE(EAP_BGP, 0x02)); + + if (!e || e->type != EAF_TYPE_AS_PATH) + runtime("Missing AS_PATH attribute"); + + as_path_get_last(e->u.ptr, &as); + } + + struct roa_table_config *rtc = ((struct f_inst_roa_check *) what)->rtc; + if (!rtc->table) + runtime("Missing ROA table"); + + res.type = T_ENUM_ROA; + res.val.i = roa_check(rtc->table, v1.val.px.ip, v1.val.px.len, as); + break; + default: bug( "Unknown instruction %d (%c)", what->code, what->code & 0xff); } @@ -1332,6 +1364,13 @@ i_same(struct f_inst *f1, struct f_inst *f2) case P('C','a'): TWOARGS; break; case P('a','f'): case P('a','l'): ONEARG; break; + case P('R','C'): + TWOARGS; + /* Does not really make sense - ROA check resuls may change anyway */ + if (strcmp(((struct f_inst_roa_check *) f1)->rtc->name, + ((struct f_inst_roa_check *) f2)->rtc->name)) + return 0; + break; default: bug( "Unknown instruction %d in same (%c)", f1->code, f1->code & 0xff); } diff --git a/filter/filter.h b/filter/filter.h index 2cf4652d..2386fc95 100644 --- a/filter/filter.h +++ b/filter/filter.h @@ -32,6 +32,12 @@ struct f_inst { /* Instruction */ #define arg1 a1.p #define arg2 a2.p +/* Not enough fields in f_inst for three args used by roa_check() */ +struct f_inst_roa_check { + struct f_inst i; + struct roa_table_config *rtc; +}; + struct f_prefix { ip_addr ip; int len; @@ -66,6 +72,8 @@ struct f_inst *f_new_inst(void); struct f_inst *f_new_dynamic_attr(int type, int f_type, int code); /* Type as core knows it, type as filters know it, and code of dynamic attribute */ struct f_tree *f_new_tree(void); struct f_inst *f_generate_complex(int operation, int operation_aux, struct f_inst *dyn, struct f_inst *argument); +struct f_inst *f_generate_roa_check(struct symbol *sym, struct f_inst *prefix, struct f_inst *asn); + struct f_tree *build_tree(struct f_tree *); struct f_tree *find_tree(struct f_tree *t, struct f_val val); @@ -141,6 +149,7 @@ int tree_compare(const void *p1, const void *p2); #define T_ENUM_SCOPE 0x32 #define T_ENUM_RTC 0x33 #define T_ENUM_RTD 0x34 +#define T_ENUM_ROA 0x35 /* new enums go here */ #define T_ENUM_EMPTY 0x3f /* Special hack for atomic_aggr */ diff --git a/filter/test.conf b/filter/test.conf index dbb05de8..64e6d91b 100644 --- a/filter/test.conf +++ b/filter/test.conf @@ -52,6 +52,32 @@ function fifteen() return 15; } +roa table rl +{ + roa 10.110.0.0/16 max 16 as 1000; + roa 10.120.0.0/16 max 24 as 1000; + roa 10.130.0.0/16 max 24 as 2000; + roa 10.130.128.0/18 max 24 as 3000; +} + +function test_roa() +{ + # cannot be tested in __startup(), sorry + print "Testing ROA"; + print "Should be true: ", roa_check(rl, 10.10.0.0/16, 1000) = ROA_UNKNOWN, + " ", roa_check(rl, 10.0.0.0/8, 1000) = ROA_UNKNOWN, + " ", roa_check(rl, 10.110.0.0/16, 1000) = ROA_VALID, + " ", roa_check(rl, 10.110.0.0/16, 2000) = ROA_INVALID, + " ", roa_check(rl, 10.110.32.0/20, 1000) = ROA_INVALID, + " ", roa_check(rl, 10.120.32.0/20, 1000) = ROA_VALID; + print "Should be true: ", roa_check(rl, 10.120.32.0/20, 2000) = ROA_INVALID, + " ", roa_check(rl, 10.120.32.32/28, 1000) = ROA_INVALID, + " ", roa_check(rl, 10.130.130.0/24, 1000) = ROA_INVALID, + " ", roa_check(rl, 10.130.130.0/24, 2000) = ROA_VALID, + " ", roa_check(rl, 10.130.30.0/24, 3000) = ROA_INVALID, + " ", roa_check(rl, 10.130.130.0/24, 3000) = ROA_VALID; +} + function paths() bgpmask pm1; bgpmask pm2; @@ -163,7 +189,7 @@ eclist el2; print "eclist A isect B: ", filter( el, el2 ); print "eclist A \ B: ", delete( el, el2 ); - +# test_roa(); } function bla() diff --git a/lib/ip.h b/lib/ip.h index 5d74d21e..023c1064 100644 --- a/lib/ip.h +++ b/lib/ip.h @@ -15,7 +15,10 @@ #include "ipv6.h" #endif -#define ipa_in_net(x,n,p) (!ipa_nonzero(ipa_and(ipa_xor((n),(x)),ipa_mkmask(p)))) +#define ipa_zero(x) (!ipa_nonzero(x)) +#define ip_is_prefix(a,l) (!ipa_nonzero(ipa_and(a, ipa_not(ipa_mkmask(l))))) +#define ipa_in_net(x,n,p) (ipa_zero(ipa_and(ipa_xor((n),(x)),ipa_mkmask(p)))) +#define net_in_net(n1,l1,n2,l2) (((l1) >= (l2)) && (ipa_zero(ipa_and(ipa_xor((n1),(n2)),ipa_mkmask(l2))))) /* * ip_classify() returns either a negative number for invalid addresses @@ -50,9 +53,6 @@ struct prefix { unsigned int len; }; -#define ip_is_prefix(a,l) (!ipa_nonzero(ipa_and(a, ipa_not(ipa_mkmask(l))))) -#define ipa_zero(x) (!ipa_nonzero(x)) - static inline int ipa_classify_net(ip_addr a) { return ipa_zero(a) ? (IADDR_HOST | SCOPE_UNIVERSE) : ipa_classify(a); } diff --git a/nest/Makefile b/nest/Makefile index 478a82b7..e6928668 100644 --- a/nest/Makefile +++ b/nest/Makefile @@ -1,4 +1,4 @@ -source=rt-table.c rt-fib.c rt-attr.c proto.c iface.c rt-dev.c password.c cli.c locks.c cmds.c neighbor.c \ +source=rt-table.c rt-fib.c rt-attr.c rt-roa.c proto.c iface.c rt-dev.c password.c cli.c locks.c cmds.c neighbor.c \ a-path.c a-set.c root-rel=../ dir-name=nest diff --git a/nest/cmds.c b/nest/cmds.c index 62d7c9b4..2a803930 100644 --- a/nest/cmds.c +++ b/nest/cmds.c @@ -72,6 +72,7 @@ print_size(char *dsc, size_t val) extern pool *rt_table_pool; extern pool *rta_pool; +extern pool *roa_pool; extern pool *proto_pool; void @@ -80,6 +81,7 @@ cmd_show_memory(void) cli_msg(-1018, "BIRD memory usage"); print_size("Routing tables:", rmemsize(rt_table_pool)); print_size("Route attributes:", rmemsize(rta_pool)); + print_size("ROA tables:", rmemsize(roa_pool)); print_size("Protocols:", rmemsize(proto_pool)); print_size("Total:", rmemsize(&root_pool)); cli_msg(0, ""); diff --git a/nest/config.Y b/nest/config.Y index f6795df4..24ef58d0 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -19,6 +19,7 @@ CF_DEFINES static struct proto_config *this_proto; static struct iface_patt *this_ipatt; static struct iface_patt_node *this_ipn; +static struct roa_table_config *this_roa_table; static list *this_p_list; static struct password_item *this_p_item; static int password_id; @@ -44,7 +45,7 @@ CF_DECLS CF_KEYWORDS(ROUTER, ID, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED, DEBUG, ALL, OFF, DIRECT) CF_KEYWORDS(INTERFACE, IMPORT, EXPORT, FILTER, NONE, TABLE, STATES, ROUTES, FILTERS) CF_KEYWORDS(PASSWORD, FROM, PASSIVE, TO, ID, EVENTS, PACKETS, PROTOCOLS, INTERFACES) -CF_KEYWORDS(PRIMARY, STATS, COUNT, FOR, COMMANDS, PREEXPORT, GENERATE) +CF_KEYWORDS(PRIMARY, STATS, COUNT, FOR, COMMANDS, PREEXPORT, GENERATE, ROA, XROA, MAX, FLUSH) CF_KEYWORDS(LISTEN, BGP, V6ONLY, DUAL, ADDRESS, PORT, PASSWORDS, DESCRIPTION) CF_KEYWORDS(RELOAD, IN, OUT, MRTDUMP, MESSAGES, RESTRICT, MEMORY, IGP_METRIC) @@ -53,14 +54,17 @@ CF_ENUM(T_ENUM_RTS, RTS_, DUMMY, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIREC CF_ENUM(T_ENUM_SCOPE, SCOPE_, HOST, LINK, SITE, ORGANIZATION, UNIVERSE, UNDEFINED) CF_ENUM(T_ENUM_RTC, RTC_, UNICAST, BROADCAST, MULTICAST, ANYCAST) CF_ENUM(T_ENUM_RTD, RTD_, ROUTER, DEVICE, BLACKHOLE, UNREACHABLE, PROHIBIT, MULTIPATH) +CF_ENUM(T_ENUM_ROA, ROA_, UNKNOWN, VALID, INVALID) %type idval %type imexport %type rtable %type optsym %type r_args +%type roa_args +%type roa_table_arg %type sym_args -%type proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_or_preexport +%type proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_or_preexport roa_mode %type proto_patt proto_patt2 CF_GRAMMAR @@ -113,6 +117,24 @@ newtab: TABLE SYM { } ; +CF_ADDTO(conf, roa_table) + +roa_table_start: ROA TABLE SYM { + this_roa_table = roa_new_table_config($3); +}; + +roa_table_opts: + /* empty */ + | roa_table_opts ROA prefix MAX NUM AS NUM ';' { + roa_add_item_config(this_roa_table, $3.addr, $3.len, $5, $7); + } + ; + +roa_table: + roa_table_start + | roa_table_start '{' roa_table_opts '}' + ; + /* Definition of protocols */ CF_ADDTO(conf, proto) @@ -433,7 +455,44 @@ export_or_preexport: | EXPORT { $$ = 2; } ; -CF_CLI(SHOW SYMBOLS, sym_args, [table|filter|function|protocol|template|], [[Show all known symbolic names]]) + +CF_CLI(SHOW XROA, roa_args, [ | in | for ] [as ] [table ], [[Show ROA table]]) +{ roa_show($3); } ; + +roa_args: + /* empty */ { + $$ = cfg_allocz(sizeof(struct roa_show_data)); + $$->mode = ROA_SHOW_ALL; + $$->table = roa_table_default; + if (roa_table_default == NULL) + cf_error("No ROA table defined"); + } + | roa_args roa_mode prefix { + $$ = $1; + if ($$->mode != ROA_SHOW_ALL) cf_error("Only one prefix expected"); + $$->prefix = $3.addr; + $$->pxlen = $3.len; + $$->mode = $2; + } + | roa_args AS NUM { + $$ = $1; + $$->asn = $3; + } + | roa_args TABLE SYM { + $$ = $1; + if ($3->class != SYM_ROA) cf_error("%s is not a ROA table", $3->name); + $$->table = ((struct roa_table_config *)$3->def)->table; + } + ; + +roa_mode: + { $$ = ROA_SHOW_PX; } + | IN { $$ = ROA_SHOW_IN; } + | FOR { $$ = ROA_SHOW_FOR; } + ; + + +CF_CLI(SHOW SYMBOLS, sym_args, [table|filter|function|protocol|template|roa|], [[Show all known symbolic names]]) { cmd_show_symbols($3); } ; sym_args: @@ -445,9 +504,34 @@ 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 ROA { $$ = $1; $$->type = SYM_ROA; } | sym_args SYM { $$ = $1; $$->sym = $2; } ; + +roa_table_arg: + /* empty */ { + if (roa_table_default == NULL) + cf_error("No ROA table defined"); + $$ = roa_table_default; + } + | TABLE SYM { + if ($2->class != SYM_ROA) + cf_error("%s is not a ROA table", $2->name); + $$ = ((struct roa_table_config *)$2->def)->table; + } + ; + +CF_CLI(ADD ROA, prefix MAX NUM AS NUM roa_table_arg, max as [table ], [[Add ROA record]]) +{ roa_add_item($8, $3.addr, $3.len, $5, $7, ROA_SRC_DYNAMIC); cli_msg(0, ""); } ; + +CF_CLI(DELETE ROA, prefix MAX NUM AS NUM roa_table_arg, max as [table ], [[Delete ROA record]]) +{ roa_delete_item($8, $3.addr, $3.len, $5, $7, ROA_SRC_DYNAMIC); cli_msg(0, ""); } ; + +CF_CLI(FLUSH ROA, roa_table_arg, [table ], [[Removes all dynamic ROA records]]) +{ roa_flush($3, ROA_SRC_DYNAMIC); cli_msg(0, ""); } ; + + CF_CLI_HELP(DUMP, ..., [[Dump debugging information]]) CF_CLI(DUMP RESOURCES,,, [[Dump all allocated resource]]) { rdump(&root_pool); cli_msg(0, ""); } ; diff --git a/nest/route.h b/nest/route.h index e6712c64..ea948838 100644 --- a/nest/route.h +++ b/nest/route.h @@ -454,4 +454,84 @@ extern struct protocol *attr_class_to_protocol[EAP_MAX]; #define DEF_PREF_PIPE 70 /* Routes piped from other tables */ #define DEF_PREF_INHERITED 10 /* Routes inherited from other routing daemons */ + +/* + * Route Origin Authorization + */ + +struct roa_item { + u32 asn; + byte maxlen; + byte src; + struct roa_item *next; +}; + +struct roa_node { + struct fib_node n; + struct roa_item *items; + // u32 cached_asn; +}; + +struct roa_table { + node n; /* Node in roa_table_list */ + struct fib fib; + char *name; /* Name of this ROA table */ + struct roa_table_config *cf; /* Configuration of this ROA table */ +}; + +struct roa_item_config { + ip_addr prefix; + byte pxlen, maxlen; + u32 asn; + struct roa_item_config *next; +}; + +struct roa_table_config { + node n; /* Node in config->rpa_tables */ + char *name; /* Name of this ROA table */ + struct roa_table *table; + + struct roa_item_config *roa_items; /* Preconfigured ROA items */ + + // char *filename; + // int gc_max_ops; /* Maximum number of operations before GC is run */ + // int gc_min_time; /* Minimum time between two consecutive GC runs */ +}; + +struct roa_show_data { + struct fib_iterator fit; + struct roa_table *table; + ip_addr prefix; + byte pxlen; + byte mode; /* ROA_SHOW_* values */ + u32 asn; /* Filter ASN, 0 -> all */ +}; + +#define ROA_UNKNOWN 0 +#define ROA_VALID 1 +#define ROA_INVALID 2 + +#define ROA_SRC_ANY 0 +#define ROA_SRC_CONFIG 1 +#define ROA_SRC_DYNAMIC 2 + +#define ROA_SHOW_ALL 0 +#define ROA_SHOW_PX 1 +#define ROA_SHOW_IN 2 +#define ROA_SHOW_FOR 3 + +extern struct roa_table *roa_table_default; + +void roa_add_item(struct roa_table *t, ip_addr prefix, byte pxlen, byte maxlen, u32 asn, byte src); +void roa_delete_item(struct roa_table *t, ip_addr prefix, byte pxlen, byte maxlen, u32 asn, byte src); +void roa_flush(struct roa_table *t, byte src); +byte roa_check(struct roa_table *t, ip_addr prefix, byte pxlen, u32 asn); +struct roa_table_config * roa_new_table_config(struct symbol *s); +void roa_add_item_config(struct roa_table_config *rtc, ip_addr prefix, byte pxlen, byte maxlen, u32 asn); +void roa_init(void); +void roa_preconfig(struct config *c); +void roa_commit(struct config *new, struct config *old); +void roa_show(struct roa_show_data *d); + + #endif diff --git a/nest/rt-roa.c b/nest/rt-roa.c new file mode 100644 index 00000000..f94842c4 --- /dev/null +++ b/nest/rt-roa.c @@ -0,0 +1,440 @@ +/* + * BIRD -- Route Origin Authorization + * + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#undef LOCAL_DEBUG + +#include "nest/bird.h" +#include "nest/route.h" +#include "nest/cli.h" +#include "lib/lists.h" +#include "lib/resource.h" +#include "lib/event.h" +#include "lib/string.h" +#include "conf/conf.h" + + +pool *roa_pool; +static slab *roa_slab; /* Slab of struct roa_item */ +static list roa_table_list; /* List of struct roa_table */ +struct roa_table *roa_table_default; /* The first ROA table in the config */ + +static inline int +src_match(struct roa_item *it, byte src) +{ return !src || it->src == src; } + +/** + * roa_add_item - add a ROA entry + * @t: ROA table + * @prefix: prefix of the ROA entry + * @pxlen: prefix length of the ROA entry + * @maxlen: max length field of the ROA entry + * @asn: AS number field of the ROA entry + * @src: source of the ROA entry (ROA_SRC_*) + * + * The function adds a new ROA entry to the ROA table. If the same ROA + * is already in the table, nothing is added. @src field is used to + * distinguish different sources of ROAs. + */ +void +roa_add_item(struct roa_table *t, ip_addr prefix, byte pxlen, byte maxlen, u32 asn, byte src) +{ + struct roa_node *n = fib_get(&t->fib, &prefix, pxlen); + + // if ((n->items == NULL) && (n->n.x0 != ROA_INVALID)) + // t->cached_items--; + + struct roa_item *it; + for (it = n->items; it; it = it->next) + if ((it->maxlen == maxlen) && (it->asn == asn) && src_match(it, src)) + return; + + it = sl_alloc(roa_slab); + it->asn = asn; + it->maxlen = maxlen; + it->src = src; + it->next = n->items; + n->items = it; +} + +/** + * roa_delete_item - delete a ROA entry + * @t: ROA table + * @prefix: prefix of the ROA entry + * @pxlen: prefix length of the ROA entry + * @maxlen: max length field of the ROA entry + * @asn: AS number field of the ROA entry + * @src: source of the ROA entry (ROA_SRC_*) + * + * The function removes a specified ROA entry from the ROA table and + * frees it. If @src field is not ROA_SRC_ANY, only entries from + * that source are considered. + */ +void +roa_delete_item(struct roa_table *t, ip_addr prefix, byte pxlen, byte maxlen, u32 asn, byte src) +{ + struct roa_node *n = fib_find(&t->fib, &prefix, pxlen); + + if (!n) + return; + + struct roa_item *it, **itp; + for (itp = &n->items; it = *itp; itp = &it->next) + if ((it->maxlen == maxlen) && (it->asn == asn) && src_match(it, src)) + break; + + if (!it) + return; + + *itp = it->next; + sl_free(roa_slab, it); + + // if ((n->items == NULL) && (n->n.x0 != ROA_INVALID)) + // t->cached_items++; +} + + +/** + * roa_flush - flush a ROA table + * @t: ROA table + * @src: source of ROA entries (ROA_SRC_*) + * + * The function removes and frees ROA entries from the ROA table. If + * @src is ROA_SRC_ANY, all entries in the table are removed, + * otherwise only all entries from that source are removed. + */ +void +roa_flush(struct roa_table *t, byte src) +{ + struct roa_item *it, **itp; + struct roa_node *n; + + FIB_WALK(&t->fib, fn) + { + n = (struct roa_node *) fn; + + itp = &n->items; + while (it = *itp) + if (src_match(it, src)) + { + *itp = it->next; + sl_free(roa_slab, it); + } + else + itp = &it->next; + } + FIB_WALK_END; + + // TODO add cleanup of roa_nodes +} + + + +/* +byte +roa_check(struct roa_table *t, ip_addr prefix, byte pxlen, u32 asn) +{ + struct roa_node *n = fib_find(&t->fib, &prefix, pxlen); + + if (n && n->n.x0 == ROA_UNKNOWN) + return ROA_UNKNOWN; + + if (n && n->n.x0 == ROA_VALID && asn == n->cached_asn) + return ROA_VALID; + + byte rv = roa_match(t, n, prefix, pxlen, asn); + + if (rv != ROA_INVALID) + { + if (!n) + { + if (t->cached_items >= t->cached_items_max) + n = fib_get(&t->fib, &prefix, pxlen); + t->cached_items++; + } + + n->cached_asn = asn; + n->n.x0 = rv; + } + + return rv; +} +*/ + +/** + * roa_check - check validity of route origination in a ROA table + * @t: ROA table + * @prefix: network prefix to check + * @pxlen: length of network prefix + * @asn: AS number of network prefix + * + * Implements RFC 6483 route validation for the given network + * prefix. The procedure is to find all candidate ROAs - ROAs whose + * prefixes cover the give network prefix. If there is no candidate + * ROA, return ROA_UNKNOWN. If there is a candidate ROA with matching + * ASN and maxlen field greater than or equal to the given prefix + * length, return ROA_VALID. Otherwise return ROA_INVALID. If caller + * cannot determine origin AS, 0 could be used (in that case ROA_VALID + * cannot happen). + */ +byte +roa_check(struct roa_table *t, ip_addr prefix, byte pxlen, u32 asn) +{ + struct roa_node *n; + ip_addr px; + byte anything = 0; + + int len; + for (len = pxlen; len >= 0; len--) + { + px = ipa_and(prefix, ipa_mkmask(len)); + n = fib_find(&t->fib, &px, len); + + if (!n) + continue; + + struct roa_item *it; + for (it = n->items; it; it = it->next) + { + anything = 1; + if ((it->maxlen >= pxlen) && (it->asn == asn) && asn) + return ROA_VALID; + } + } + + return anything ? ROA_INVALID : ROA_UNKNOWN; +} + +static void +roa_node_init(struct fib_node *fn) +{ + struct roa_node *n = (struct roa_node *) fn; + n->items = NULL; +} + +static inline void +roa_populate(struct roa_table *t) +{ + struct roa_item_config *ric; + for (ric = t->cf->roa_items; ric; ric = ric->next) + roa_add_item(t, ric->prefix, ric->pxlen, ric->maxlen, ric->asn, ROA_SRC_CONFIG); +} + +static void +roa_new_table(struct roa_table_config *cf) +{ + struct roa_table *t; + + t = mb_allocz(roa_pool, sizeof(struct roa_table)); + fib_init(&t->fib, roa_pool, sizeof(struct roa_node), 0, roa_node_init); + t->name = cf->name; + t->cf = cf; + + cf->table = t; + add_tail(&roa_table_list, &t->n); + + roa_populate(t); +} + +struct roa_table_config * +roa_new_table_config(struct symbol *s) +{ + struct roa_table_config *rtc = cfg_allocz(sizeof(struct roa_table_config)); + + cf_define_symbol(s, SYM_ROA, rtc); + rtc->name = s->name; + add_tail(&new_config->roa_tables, &rtc->n); + return rtc; +} + +/** + * roa_add_item_config - add a static ROA entry to a ROA table configuration + * + * Arguments are self-explanatory. The first is the ROA table config, rest + * are specifying the ROA entry. + */ +void +roa_add_item_config(struct roa_table_config *rtc, ip_addr prefix, byte pxlen, byte maxlen, u32 asn) +{ + struct roa_item_config *ric = cfg_allocz(sizeof(struct roa_item_config)); + + ric->prefix = prefix; + ric->pxlen = pxlen; + ric->maxlen = maxlen; + ric->asn = asn; + ric->next = rtc->roa_items; + rtc->roa_items = ric; +} + +/** + * roa_init - initialize ROA tables + * + * This function is called during BIRD startup. It initializes + * the ROA table module. + */ +void +roa_init(void) +{ + roa_pool = rp_new(&root_pool, "ROA tables"); + roa_slab = sl_new(roa_pool, sizeof(struct roa_item)); + init_list(&roa_table_list); +} + +void +roa_preconfig(struct config *c) +{ + init_list(&c->roa_tables); +} + + +/** + * roa_commit - commit new ROA table configuration + * @new: new configuration + * @old: original configuration or %NULL if it's boot time config + * + * Scan differences between @old and @new configuration and modify the + * ROA tables according to these changes. If @new defines a previously + * unknown table, create it, if it omits a table existing in @old, + * delete it (there are no references, only indirect through struct + * roa_table_config). If it exists in both configurations, update the + * configured ROA entries. + */ +void +roa_commit(struct config *new, struct config *old) +{ + struct roa_table_config *cf; + struct roa_table *t; + + if (old) + WALK_LIST(t, roa_table_list) + { + struct symbol *sym = cf_find_symbol(t->name); + if (sym && sym->class == SYM_ROA) + { + /* Found old table in new config */ + cf = sym->def; + cf->table = t; + t->name = cf->name; + t->cf = cf; + + /* Reconfigure it */ + roa_flush(t, ROA_SRC_CONFIG); + roa_populate(t); + } + else + { + t->cf->table = NULL; + + /* Free it now */ + roa_flush(t, ROA_SRC_ANY); + rem_node(&t->n); + fib_free(&t->fib); + mb_free(t); + } + } + + /* Add new tables */ + WALK_LIST(cf, new->roa_tables) + if (! cf->table) + roa_new_table(cf); + + roa_table_default = EMPTY_LIST(new->roa_tables) ? NULL : + ((struct roa_table_config *) HEAD(new->roa_tables))->table; +} + + + +static void +roa_show_node(struct cli *c, struct roa_node *rn, int len, u32 asn) +{ + struct roa_item *ri; + + for (ri = rn->items; ri; ri = ri->next) + if ((ri->maxlen >= len) && (!asn || (ri->asn == asn))) + cli_printf(c, -1111, "%I/%d max %d as %u", rn->n.prefix, rn->n.pxlen, ri->maxlen, ri->asn); +} + +static void +roa_show_cont(struct cli *c) +{ + struct roa_show_data *d = c->rover; + struct fib *fib = &d->table->fib; + struct fib_iterator *it = &d->fit; + struct roa_node *rn; + unsigned max = 32; + + FIB_ITERATE_START(fib, it, f) + { + rn = (struct roa_node *) f; + + if (!max--) + { + FIB_ITERATE_PUT(it, f); + return; + } + + if ((d->mode == ROA_SHOW_ALL) || + net_in_net(rn->n.prefix, rn->n.pxlen, d->prefix, d->pxlen)) + roa_show_node(c, rn, 0, d->asn); + } + FIB_ITERATE_END(f); + + cli_printf(c, 0, ""); + c->cont = c->cleanup = NULL; +} + +static void +roa_show_cleanup(struct cli *c) +{ + struct roa_show_data *d = c->rover; + + /* Unlink the iterator */ + fit_get(&d->table->fib, &d->fit); +} + +void +roa_show(struct roa_show_data *d) +{ + struct roa_node *rn; + ip_addr px; + int len; + + switch (d->mode) + { + case ROA_SHOW_ALL: + case ROA_SHOW_IN: + FIB_ITERATE_INIT(&d->fit, &d->table->fib); + this_cli->cont = roa_show_cont; + this_cli->cleanup = roa_show_cleanup; + this_cli->rover = d; + break; + + case ROA_SHOW_PX: + rn = fib_find(&d->table->fib, &d->prefix, d->pxlen); + if (rn) + { + roa_show_node(this_cli, rn, 0, d->asn); + cli_msg(0, ""); + } + else + cli_msg(-8001, "Network not in table"); + break; + + case ROA_SHOW_FOR: + for (len = d->pxlen; len >= 0; len--) + { + px = ipa_and(d->prefix, ipa_mkmask(len)); + rn = fib_find(&d->table->fib, &px, len); + + if (!rn) + continue; + + roa_show_node(this_cli, rn, 0, d->asn); + } + cli_msg(0, ""); + break; + } +} diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c index d6176841..49299411 100644 --- a/sysdep/unix/main.c +++ b/sysdep/unix/main.c @@ -643,6 +643,7 @@ main(int argc, char **argv) io_init(); rt_init(); if_init(); + roa_init(); uid_t use_uid = get_uid(use_user); gid_t use_gid = get_gid(use_group);