diff --git a/conf/cf-lex.l b/conf/cf-lex.l index 263311c2..6d22f64e 100644 --- a/conf/cf-lex.l +++ b/conf/cf-lex.l @@ -934,6 +934,10 @@ cf_symbol_class_name(struct symbol *sym) return "routing table"; case SYM_ATTRIBUTE: return "custom attribute"; + case SYM_MPLS_DOMAIN: + return "MPLS domain"; + case SYM_MPLS_RANGE: + return "MPLS label range"; case SYM_KEYWORD: return "symbol"; case SYM_CONSTANT_RANGE: diff --git a/conf/conf.c b/conf/conf.c index 84a4c048..c17358c1 100644 --- a/conf/conf.c +++ b/conf/conf.c @@ -49,6 +49,7 @@ #include "nest/route.h" #include "nest/protocol.h" #include "nest/iface.h" +#include "nest/mpls.h" #include "lib/resource.h" #include "lib/string.h" #include "lib/event.h" @@ -141,6 +142,7 @@ config_parse(struct config *c) cf_lex_init(0, c); sysdep_preconfig(c); protos_preconfig(c); + mpls_preconfig(c); rt_preconfig(c); cf_parse(); rt_postconfig(c); @@ -300,6 +302,7 @@ config_do_commit(struct config *c, int type) int force_restart = sysdep_commit(c, old_config); DBG("global_commit\n"); force_restart |= global_commit(c, old_config); + mpls_commit(c, old_config); DBG("rt_commit\n"); rt_commit(c, old_config); DBG("protos_commit\n"); @@ -548,6 +551,7 @@ order_shutdown(int gr) memcpy(c, config, sizeof(struct config)); init_list(&c->protos); init_list(&c->tables); + init_list(&c->mpls_domains); init_list(&c->symbols); memset(c->def_tables, 0, sizeof(c->def_tables)); c->shutdown = 1; diff --git a/conf/conf.h b/conf/conf.h index 841d5c1f..691c8668 100644 --- a/conf/conf.h +++ b/conf/conf.h @@ -21,6 +21,7 @@ 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 mpls_domains; /* Configured MPLS domains (struct mpls_domain_config) */ list logfiles; /* Configured log files (sysdep) */ list tests; /* Configured unit tests (f_bt_test_suite) */ list symbols; /* Configured symbols in config order */ @@ -133,6 +134,8 @@ struct symbol { const struct filter *filter; /* For SYM_FILTER */ struct rtable_config *table; /* For SYM_TABLE */ struct ea_class *attribute; /* For SYM_ATTRIBUTE */ + struct mpls_domain_config *mpls_domain; /* For SYM_MPLS_DOMAIN */ + struct mpls_range_config *mpls_range; /* For SYM_MPLS_RANGE */ struct f_val *val; /* For SYM_CONSTANT */ uint offset; /* For SYM_VARIABLE */ const struct keyword *keyword; /* For SYM_KEYWORD */ @@ -173,6 +176,8 @@ extern linpool *global_root_scope_linpool; #define SYM_ATTRIBUTE 6 #define SYM_KEYWORD 7 #define SYM_METHOD 8 +#define SYM_MPLS_DOMAIN 9 +#define SYM_MPLS_RANGE 10 #define SYM_VARIABLE 0x100 /* 0x100-0x1ff are variable types */ #define SYM_VARIABLE_RANGE SYM_VARIABLE ... (SYM_VARIABLE | 0xff) diff --git a/conf/confbase.Y b/conf/confbase.Y index 37198751..380baa61 100644 --- a/conf/confbase.Y +++ b/conf/confbase.Y @@ -44,6 +44,8 @@ static inline void cf_assert_symbol(const struct symbol *sym, uint class) { case SYM_FILTER: cf_assert(sym->class == SYM_FILTER, "Filter name required"); break; case SYM_TABLE: cf_assert(sym->class == SYM_TABLE, "Table name required"); break; case SYM_ATTRIBUTE: cf_assert(sym->class == SYM_ATTRIBUTE, "Custom attribute name required"); break; + case SYM_MPLS_DOMAIN: cf_assert(sym->class == SYM_MPLS_DOMAIN, "MPLS domain name required"); break; + case SYM_MPLS_RANGE: cf_assert(sym->class == SYM_MPLS_RANGE, "MPLS range name required"); break; case SYM_VARIABLE: cf_assert((sym->class & ~0xff) == SYM_VARIABLE, "Variable name required"); break; case SYM_CONSTANT: cf_assert((sym->class & ~0xff) == SYM_CONSTANT, "Constant name required"); break; default: bug("This shall not happen"); diff --git a/lib/ip.h b/lib/ip.h index e92c338a..f2650d3f 100644 --- a/lib/ip.h +++ b/lib/ip.h @@ -385,7 +385,13 @@ static inline ip6_addr ip6_hton(ip6_addr a) static inline ip6_addr ip6_ntoh(ip6_addr a) { return _MI6(ntohl(_I0(a)), ntohl(_I1(a)), ntohl(_I2(a)), ntohl(_I3(a))); } -#define MPLS_MAX_LABEL_STACK 16 +#define MPLS_MAX_LABEL 0x100000 + +#define MPLS_MAX_LABEL_STACK 8 +typedef struct mpls_label_stack { + uint len; + u32 stack[MPLS_MAX_LABEL_STACK]; +} mpls_label_stack; static inline int mpls_get(const char *buf, int buflen, u32 *stack) diff --git a/lib/route.h b/lib/route.h index 06772577..9fcf2061 100644 --- a/lib/route.h +++ b/lib/route.h @@ -452,6 +452,12 @@ u32 rt_get_igp_metric(const rte *rt); /* From: Advertising router */ extern struct ea_class ea_gen_from; + +/* MPLS Label, Policy and Class */ +extern struct ea_class ea_gen_mpls_label, + ea_gen_mpls_policy, ea_gen_mpls_class; + + /* Source: An old method to devise the route source protocol and kind. * To be superseded in a near future by something more informative. */ extern struct ea_class ea_gen_source; diff --git a/lib/type.h b/lib/type.h index 32f16382..e07c98ec 100644 --- a/lib/type.h +++ b/lib/type.h @@ -91,6 +91,7 @@ enum btype { T_ENUM_RTS = 0x31, T_ENUM_SCOPE = 0x33, + T_ENUM_MPLS_POLICY = 0x35, T_ENUM_RTD = 0x37, T_ENUM_ROA = 0x39, T_ENUM_NETTYPE = 0x3b, diff --git a/nest/Doc b/nest/Doc index 38af0feb..3be43a71 100644 --- a/nest/Doc +++ b/nest/Doc @@ -6,6 +6,7 @@ D proto.sgml S proto.c S proto-hooks.c S iface.c +S mpls.c S neighbor.c S cli.c S locks.c diff --git a/nest/Makefile b/nest/Makefile index bb141f5c..b7599245 100644 --- a/nest/Makefile +++ b/nest/Makefile @@ -1,7 +1,8 @@ -src := cli.c cmds.c iface.c locks.c neighbor.c password.c proto.c proto-build.c rt-attr.c rt-dev.c rt-fib.c rt-show.c rt-table.c +src := cli.c cmds.c iface.c locks.c mpls.c neighbor.c password.c proto.c proto-build.c rt-attr.c rt-dev.c rt-fib.c rt-show.c rt-table.c obj := $(src-o-files) $(all-daemon) $(cf-local) +$(conf-y-targets): $(s)mpls.Y $(o)proto-build.c: Makefile $(lastword $(MAKEFILE_LIST)) $(objdir)/.dir-stamp $(E)echo GEN $@ diff --git a/nest/config.Y b/nest/config.Y index 8c06126e..f212e711 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -12,6 +12,7 @@ CF_HDR #include "nest/rt-dev.h" #include "nest/password.h" #include "nest/cmds.h" +#include "nest/mpls.h" #include "lib/lists.h" #include "lib/mac.h" @@ -164,6 +165,7 @@ CF_KEYWORDS(GRACEFUL, RESTART, WAIT, MAX, AS) CF_KEYWORDS(MIN, IDLE, RX, TX, INTERVAL, MULTIPLIER, PASSIVE) CF_KEYWORDS(CHECK, LINK) CF_KEYWORDS(CORK, SORTED, TRIE, MIN, MAX, ROA, ROUTE, REFRESH, SETTLE, TIME, GC, THRESHOLD, PERIOD) +CF_KEYWORDS(MPLS_LABEL, MPLS_POLICY, MPLS_CLASS) /* For r_args_channel */ CF_KEYWORDS(IPV4, IPV4_MC, IPV4_MPLS, IPV6, IPV6_MC, IPV6_MPLS, IPV6_SADR, VPN4, VPN4_MC, VPN4_MPLS, VPN6, VPN6_MC, VPN6_MPLS, ROA4, ROA6, FLOW4, FLOW6, MPLS, PRI, SEC) @@ -174,6 +176,7 @@ CF_ENUM(T_ENUM_SCOPE, SCOPE_, HOST, LINK, SITE, ORGANIZATION, UNIVERSE, UNDEFINE CF_ENUM(T_ENUM_RTD, RTD_, BLACKHOLE, UNREACHABLE, PROHIBIT) CF_ENUM(T_ENUM_ROA, ROA_, UNKNOWN, VALID, INVALID) CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6) +CF_ENUM(T_ENUM_MPLS_POLICY, MPLS_POLICY_, NONE, STATIC, PREFIX, AGGREGATE) %type idval %type imexport @@ -181,7 +184,7 @@ CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6) %type optproto %type r_args %type sym_args -%type proto_start debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_mode limit_action net_type tos password_algorithm +%type proto_start debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_mode limit_action net_type net_type_base tos password_algorithm %type proto_patt proto_patt2 %type channel_start proto_channel %type limit_spec @@ -227,7 +230,7 @@ gr_opts: GRACEFUL RESTART WAIT expr ';' { new_config->gr_wait = $4; } ; /* Network types (for tables, channels) */ -net_type: +net_type_base: IPV4 { $$ = NET_IP4; } | IPV6 { $$ = NET_IP6; } | IPV6 SADR { $$ = NET_IP6_SADR; } @@ -237,6 +240,10 @@ net_type: | ROA6 { $$ = NET_ROA6; } | FLOW4{ $$ = NET_FLOW4; } | FLOW6{ $$ = NET_FLOW6; } + ; + +net_type: + net_type_base | MPLS { $$ = NET_MPLS; } ; @@ -340,7 +347,7 @@ proto_item: ; -channel_start: net_type +channel_start: net_type_base { $$ = this_channel = channel_config_get(NULL, net_label[$1], $1, this_proto); }; diff --git a/nest/mpls.Y b/nest/mpls.Y new file mode 100644 index 00000000..c3a03671 --- /dev/null +++ b/nest/mpls.Y @@ -0,0 +1,141 @@ +/* + * BIRD Internet Routing Daemon -- MPLS Structures + * + * (c) 2022 Ondrej Zajicek + * (c) 2022 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +CF_HDR + +#include "nest/mpls.h" + +CF_DEFINES + +static struct mpls_domain_config *this_mpls_domain; +static struct mpls_range_config *this_mpls_range; + +#define MPLS_CC ((struct mpls_channel_config *) this_channel) + +CF_DECLS + +CF_KEYWORDS(MPLS, DOMAIN, LABEL, RANGE, STATIC, DYNAMIC, START, LENGTH, POLICY, PREFIX, AGGREGATE) + +%type mpls_label_policy +%type mpls_channel_start mpls_channel + +CF_GRAMMAR + +conf: mpls_domain; + +mpls_domain: mpls_domain_start mpls_domain_opt_list mpls_domain_end; + +mpls_domain_start: MPLS DOMAIN symbol { this_mpls_domain = mpls_domain_config_new($3); }; + +mpls_domain_opt: + mpls_range + ; + +mpls_domain_opts: + /* empty */ + | mpls_domain_opts mpls_domain_opt ';' + ; + +mpls_domain_opt_list: + /* empty */ + | '{' mpls_domain_opts '}' + ; + +mpls_domain_end: { mpls_domain_postconfig(this_mpls_domain); this_mpls_domain = NULL; }; + + +mpls_range: mpls_range_start mpls_range_opt_list mpls_range_end; + +mpls_range_start: LABEL RANGE symbol +{ + if (($3->class == SYM_KEYWORD) && ($3->keyword->value == STATIC)) + this_mpls_range = this_mpls_domain->static_range; + else if (($3->class == SYM_KEYWORD) && ($3->keyword->value == DYNAMIC)) + this_mpls_range = this_mpls_domain->dynamic_range; + else + this_mpls_range = mpls_range_config_new(this_mpls_domain, $3); +}; + +mpls_range_opt: + START expr { this_mpls_range->start = $2; if ($2 >= MPLS_MAX_LABEL) cf_error("MPLS label range start must be less than 2^20"); } + | LENGTH expr { this_mpls_range->length = $2; if ($2 >= MPLS_MAX_LABEL) cf_error("MPLS label range length must be less than 2^20"); if (!$2) cf_error("MPLS label range length must be nonzero"); } + ; + +mpls_range_opts: + /* empty */ + | mpls_range_opts mpls_range_opt ';' + ; + +mpls_range_opt_list: + /* empty */ + | '{' mpls_range_opts '}' + ; + +mpls_range_end: +{ + struct mpls_range_config *r = this_mpls_range; + + if ((r->start == (uint) -1) || (r->length == (uint) -1)) + cf_error("MPLS label range start and length must be specified"); + + if (r->start + r->length > MPLS_MAX_LABEL) + cf_error("MPLS label range end must be less than 2^20"); + + this_mpls_range = NULL; +}; + + +mpls_channel: mpls_channel_start mpls_channel_opt_list mpls_channel_end; + +mpls_channel_start: MPLS +{ + $$ = this_channel = channel_config_get(&channel_mpls, net_label[NET_MPLS], NET_MPLS, this_proto); + + if (EMPTY_LIST(new_config->mpls_domains)) + cf_error("No MPLS domain defined"); + + /* Default values for new channel */ + if (!MPLS_CC->domain) + { + MPLS_CC->domain = cf_default_mpls_domain(new_config); + MPLS_CC->label_policy = MPLS_POLICY_PREFIX; + } +}; + +mpls_label_policy: + STATIC { $$ = MPLS_POLICY_STATIC; } + | PREFIX { $$ = MPLS_POLICY_PREFIX; } + | AGGREGATE { $$ = MPLS_POLICY_AGGREGATE; } + ; + +mpls_channel_opt: + channel_item + | DOMAIN CF_SYM_KNOWN { cf_assert_symbol($2, SYM_MPLS_DOMAIN); MPLS_CC->domain = $2->mpls_domain; } + | LABEL RANGE CF_SYM_KNOWN { cf_assert_symbol($3, SYM_MPLS_RANGE); MPLS_CC->range = $3->mpls_range; } + | LABEL RANGE STATIC { MPLS_CC->range = MPLS_CC->domain->static_range; } + | LABEL RANGE DYNAMIC { MPLS_CC->range = MPLS_CC->domain->dynamic_range; } + | LABEL POLICY mpls_label_policy { MPLS_CC->label_policy = $3; } + ; + +mpls_channel_opts: + /* empty */ + | mpls_channel_opts mpls_channel_opt ';' + ; + +mpls_channel_opt_list: + /* empty */ + | '{' mpls_channel_opts '}' + ; + +mpls_channel_end: { mpls_channel_postconfig(this_channel); } channel_end; + + +CF_CODE + +CF_END diff --git a/nest/mpls.c b/nest/mpls.c new file mode 100644 index 00000000..96bafcb3 --- /dev/null +++ b/nest/mpls.c @@ -0,0 +1,995 @@ +/* + * BIRD Internet Routing Daemon -- MPLS Structures + * + * (c) 2022 Ondrej Zajicek + * (c) 2022 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +/** + * DOC: MPLS + * + * The MPLS subsystem manages MPLS labels and handles their allocation to + * MPLS-aware routing protocols. These labels are then attached to IP or VPN + * routes representing label switched paths -- LSPs. MPLS labels are also used + * in special MPLS routes (which use labels as network address) that are + * exported to MPLS routing table in kernel. The MPLS subsystem consists of MPLS + * domains (struct &mpls_domain), MPLS channels (struct &mpls_channel) and FEC + * maps (struct &mpls_fec_map). + * + * The MPLS domain represents one MPLS label address space, implements the label + * allocator, and handles associated configuration and management. The domain is + * declared in the configuration (struct &mpls_domain_config). There might be + * multiple MPLS domains representing separate label spaces, but in most cases + * one domain is enough. MPLS-aware protocols and routing tables are associated + * with a specific MPLS domain. + * + * The MPLS domain has configurable label ranges (struct &mpls_range), by + * default it has two ranges: static (16-1000) and dynamic (1000-10000). When + * a protocol wants to allocate labels, it first acquires a handle (struct + * &mpls_handle) for a specific range using mpls_new_handle(), and then it + * allocates labels from that with mpls_new_label(). When not needed, labels are + * freed by mpls_free_label() and the handle is released by mpls_free_handle(). + * Note that all labels and handles must be freed manually. + * + * Both MPLS domain and MPLS range are reference counted, so when deconfigured + * they could be freed just after all labels and ranges are freed. Users are + * expected to hold a reference to a MPLS domain for whole time they use + * something from that domain (e.g. &mpls_handle), but releasing reference to + * a range while holding associated handle is OK. + * + * The MPLS channel is subclass of a generic protocol channel. It has two + * distinct purposes - to handle per-protocol MPLS configuration (e.g. which + * MPLS domain is associated with the protocol, which label range is used by the + * protocol), and to announce MPLS routes to a routing table (as a regular + * protocol channel). + * + * The FEC map is a helper structure that maps forwarding equivalent classes + * (FECs) to MPLS labels. It is an internal matter of a routing protocol how to + * assign meaning to allocated labels, announce LSP routes and associated MPLS + * routes (i.e. ILM entries). But the common behavior is implemented in the FEC + * map, which can be used by the protocols that work with IP-prefix-based FECs. + * + * The FEC map keeps hash tables of FECs (struct &mpls_fec) based on network + * prefix, next hop eattr and assigned label. It has three labeling policies: + * static assignment (%MPLS_POLICY_STATIC), per-prefix policy (%MPLS_POLICY_PREFIX), + * and aggregating policy (%MPLS_POLICY_AGGREGATE). In per-prefix policy, each + * distinct LSP is a separate FEC and uses a separate label, which is kept even + * if the next hop of the LSP changes. In aggregating policy, LSPs with a same + * next hop form one FEC and use one label, but when a next hop (or remote + * label) of such LSP changes then the LSP must be moved to a different FEC and + * assigned a different label. + * + * The overall process works this way: A protocol wants to announce a LSP route, + * it does that by announcing e.g. IP route with %EA_MPLS_POLICY attribute. + * After the route is accepted by filters (which may also change the policy + * attribute or set a static label), the mpls_handle_rte() is called from + * rte_update2(), which applies selected labeling policy, finds existing FEC or + * creates a new FEC (which includes allocating new label and announcing related + * MPLS route by mpls_announce_fec()), and attach FEC label to the LSP route. + * After that, the LSP route is stored in routing table by rte_recalculate(). + * Changes in routing tables trigger mpls_rte_insert() and mpls_rte_remove() + * hooks, which refcount FEC structures and possibly trigger removal of FECs + * and withdrawal of MPLS routes. + * + * TODO: + * - show mpls labels CLI command + * - label range non-intersection check + * - better range reconfigurations (allow reduce ranges over unused labels) + * - protocols should do route refresh instead of resetart when reconfiguration + * requires changing labels (e.g. different label range) + * - registering static allocations + * - checking range in static allocations + * - special handling of reserved labels + */ + +#include "nest/bird.h" +#include "nest/route.h" +#include "nest/mpls.h" + +static struct mpls_range *mpls_new_range(struct mpls_domain *m, struct mpls_range_config *cf); +static struct mpls_range *mpls_find_range_(list *l, const char *name); +static int mpls_reconfigure_range(struct mpls_domain *m, struct mpls_range *r, struct mpls_range_config *cf); +static void mpls_remove_range(struct mpls_range *r); + + +/* + * MPLS domain + */ + +list mpls_domains; + +void +mpls_init(void) +{ + init_list(&mpls_domains); +} + +struct mpls_domain_config * +mpls_domain_config_new(struct symbol *s) +{ + struct mpls_domain_config *mc = cfg_allocz(sizeof(struct mpls_domain_config)); + struct mpls_range_config *rc; + + cf_define_symbol(new_config, s, SYM_MPLS_DOMAIN, mpls_domain, mc); + mc->name = s->name; + init_list(&mc->ranges); + + /* Predefined static range */ + rc = mpls_range_config_new(mc, NULL); + rc->name = "static"; + rc->start = 16; + rc->length = 984; + mc->static_range = rc; + + /* Predefined dynamic range */ + rc = mpls_range_config_new(mc, NULL); + rc->name = "dynamic"; + rc->start = 1000; + rc->length = 9000; + mc->dynamic_range = rc; + + add_tail(&new_config->mpls_domains, &mc->n); + + return mc; +} + +void +mpls_domain_postconfig(struct mpls_domain_config *cf UNUSED) +{ + /* Add label range non-intersection check */ +} + +static struct mpls_domain * +mpls_new_domain(struct mpls_domain_config *cf) +{ + struct pool *p = rp_new(&root_pool, the_bird_domain.the_bird, "MPLS domain"); + struct mpls_domain *m = mb_allocz(p, sizeof(struct mpls_domain)); + + m->cf = cf; + m->name = cf->name; + m->pool = p; + + lmap_init(&m->labels, p); + lmap_set(&m->labels, 0); + + init_list(&m->ranges); + init_list(&m->handles); + + struct mpls_range_config *rc; + WALK_LIST(rc, cf->ranges) + mpls_new_range(m, rc); + + add_tail(&mpls_domains, &m->n); + cf->domain = m; + + return m; +} + +static struct mpls_domain * +mpls_find_domain_(list *l, const char *name) +{ + struct mpls_domain *m; + + WALK_LIST(m, *l) + if (!strcmp(m->name, name)) + return m; + + return NULL; +} + +static int +mpls_reconfigure_domain(struct mpls_domain *m, struct mpls_domain_config *cf) +{ + cf->domain = m; + m->cf->domain = NULL; + m->cf = cf; + m->name = cf->name; + + /* Reconfigure label ranges */ + list old_ranges; + init_list(&old_ranges); + add_tail_list(&old_ranges, &m->ranges); + init_list(&m->ranges); + + struct mpls_range_config *rc; + WALK_LIST(rc, cf->ranges) + { + struct mpls_range *r = mpls_find_range_(&old_ranges, rc->name); + + if (r && mpls_reconfigure_range(m, r, rc)) + { + rem_node(&r->n); + add_tail(&m->ranges, &r->n); + continue; + } + + mpls_new_range(m, rc); + } + + struct mpls_range *r, *r2; + WALK_LIST_DELSAFE(r, r2, old_ranges) + mpls_remove_range(r); + + add_tail_list(&m->ranges, &old_ranges); + + return 1; +} + +static void +mpls_free_domain(struct mpls_domain *m) +{ + ASSERT(m->use_count == 0); + ASSERT(m->label_count == 0); + ASSERT(EMPTY_LIST(m->handles)); + + struct config *cfg = m->removed; + + m->cf->domain = NULL; + rem_node(&m->n); + rfree(m->pool); + + config_del_obstacle(cfg); +} + +static void +mpls_remove_domain(struct mpls_domain *m, struct config *cfg) +{ + m->removed = cfg; + config_add_obstacle(cfg); + + if (!m->use_count) + mpls_free_domain(m); +} + +void +mpls_lock_domain(struct mpls_domain *m) +{ + m->use_count++; +} + +void +mpls_unlock_domain(struct mpls_domain *m) +{ + ASSERT(m->use_count > 0); + + m->use_count--; + if (!m->use_count && m->removed) + mpls_free_domain(m); +} + +void +mpls_preconfig(struct config *c) +{ + init_list(&c->mpls_domains); +} + +void +mpls_commit(struct config *new, struct config *old) +{ + list old_domains; + init_list(&old_domains); + add_tail_list(&old_domains, &mpls_domains); + init_list(&mpls_domains); + + struct mpls_domain_config *mc; + WALK_LIST(mc, new->mpls_domains) + { + struct mpls_domain *m = mpls_find_domain_(&old_domains, mc->name); + + if (m && mpls_reconfigure_domain(m, mc)) + { + rem_node(&m->n); + add_tail(&mpls_domains, &m->n); + continue; + } + + mpls_new_domain(mc); + } + + struct mpls_domain *m, *m2; + WALK_LIST_DELSAFE(m, m2, old_domains) + mpls_remove_domain(m, old); + + add_tail_list(&mpls_domains, &old_domains); +} + + +/* + * MPLS range + */ + +struct mpls_range_config * +mpls_range_config_new(struct mpls_domain_config *mc, struct symbol *s) +{ + struct mpls_range_config *rc = cfg_allocz(sizeof(struct mpls_range_config)); + + if (s) + cf_define_symbol(new_config, s, SYM_MPLS_RANGE, mpls_range, rc); + + rc->domain = mc; + rc->name = s ? s->name : NULL; + rc->start = (uint) -1; + rc->length = (uint) -1; + + add_tail(&mc->ranges, &rc->n); + + return rc; +} + +static struct mpls_range * +mpls_new_range(struct mpls_domain *m, struct mpls_range_config *cf) +{ + struct mpls_range *r = mb_allocz(m->pool, sizeof(struct mpls_range)); + + r->cf = cf; + r->name = cf->name; + r->lo = cf->start; + r->hi = cf->start + cf->length; + + add_tail(&m->ranges, &r->n); + cf->range = r; + + return r; +} + +static struct mpls_range * +mpls_find_range_(list *l, const char *name) +{ + struct mpls_range *r; + + WALK_LIST(r, *l) + if (!strcmp(r->name, name)) + return r; + + return NULL; +} + +static int +mpls_reconfigure_range(struct mpls_domain *m UNUSED, struct mpls_range *r, struct mpls_range_config *cf) +{ + if ((cf->start > r->lo) || (cf->start + cf->length < r->hi)) + return 0; + + cf->range = r; + r->cf->range = NULL; + r->cf = cf; + r->name = cf->name; + r->lo = cf->start; + r->hi = cf->start + cf->length; + + return 1; +} + +static void +mpls_free_range(struct mpls_range *r) +{ + ASSERT(r->use_count == 0); + ASSERT(r->label_count == 0); + + r->cf->range = NULL; + rem_node(&r->n); + mb_free(r); +} + +static void +mpls_remove_range(struct mpls_range *r) +{ + r->removed = 1; + + if (!r->use_count) + mpls_free_range(r); +} + +void +mpls_lock_range(struct mpls_range *r) +{ + r->use_count++; +} + +void +mpls_unlock_range(struct mpls_range *r) +{ + ASSERT(r->use_count > 0); + + r->use_count--; + if (!r->use_count && r->removed) + mpls_free_range(r); +} + + +/* + * MPLS handle + */ + +struct mpls_handle * +mpls_new_handle(struct mpls_domain *m, struct mpls_range *r) +{ + struct mpls_handle *h = mb_allocz(m->pool, sizeof(struct mpls_handle)); + + h->range = r; + mpls_lock_range(h->range); + + add_tail(&m->handles, &h->n); + + return h; +} + +void +mpls_free_handle(struct mpls_domain *m UNUSED, struct mpls_handle *h) +{ + ASSERT(h->label_count == 0); + + mpls_unlock_range(h->range); + rem_node(&h->n); + mb_free(h); +} + + +/* + * MPLS label + */ + +uint +mpls_new_label(struct mpls_domain *m, struct mpls_handle *h) +{ + struct mpls_range *r = h->range; + uint n = lmap_first_zero_in_range(&m->labels, r->lo, r->hi); + + if (n >= r->hi) + return 0; + + m->label_count++; + r->label_count++; + h->label_count++; + + lmap_set(&m->labels, n); + return n; +} + +void +mpls_free_label(struct mpls_domain *m, struct mpls_handle *h, uint n) +{ + struct mpls_range *r = h->range; + + ASSERT(lmap_test(&m->labels, n)); + lmap_clear(&m->labels, n); + + ASSERT(m->label_count); + m->label_count--; + + ASSERT(r->label_count); + r->label_count--; + + ASSERT(h->label_count); + h->label_count--; +} + + +/* + * MPLS channel + */ + +static void +mpls_channel_init(struct channel *C, struct channel_config *CC) +{ + struct mpls_channel *c = (void *) C; + struct mpls_channel_config *cc = (void *) CC; + + c->domain = cc->domain->domain; + c->range = cc->range->range; + c->label_policy = cc->label_policy; +} + +static int +mpls_channel_start(struct channel *C) +{ + struct mpls_channel *c = (void *) C; + + mpls_lock_domain(c->domain); + mpls_lock_range(c->range); + + return 0; +} + +/* +static void +mpls_channel_shutdown(struct channel *C) +{ + struct mpls_channel *c = (void *) C; + +} +*/ + +static void +mpls_channel_cleanup(struct channel *C) +{ + struct mpls_channel *c = (void *) C; + + mpls_unlock_range(c->range); + mpls_unlock_domain(c->domain); +} + +static int +mpls_channel_reconfigure(struct channel *C, struct channel_config *CC, int *import_changed UNUSED, int *export_changed UNUSED) +{ + struct mpls_channel *c = (void *) C; + struct mpls_channel_config *new = (void *) CC; + + if ((new->domain->domain != c->domain) || + (new->range->range != c->range) || + (new->label_policy != c->label_policy)) + return 0; + + return 1; +} + +void +mpls_channel_postconfig(struct channel_config *CC) +{ + struct mpls_channel_config *cc = (void *) CC; + + if (!cc->domain) + cf_error("MPLS domain not specified"); + + if (!cc->range) + cc->range = (cc->label_policy == MPLS_POLICY_STATIC) ? + cc->domain->static_range : cc->domain->dynamic_range; + + if (cc->range->domain != cc->domain) + cf_error("MPLS label range from different MPLS domain"); + + if (!cc->c.table) + cf_error("Routing table not specified"); +} + +struct channel_class channel_mpls = { + .channel_size = sizeof(struct mpls_channel), + .config_size = sizeof(struct mpls_channel_config), + .init = mpls_channel_init, + .start = mpls_channel_start, +// .shutdown = mpls_channel_shutdown, + .cleanup = mpls_channel_cleanup, + .reconfigure = mpls_channel_reconfigure, +}; + + +/* + * MPLS FEC map + */ + +#define NET_KEY(fec) fec->net, fec->path_id, fec->hash +#define NET_NEXT(fec) fec->next_k +#define NET_EQ(n1,i1,h1,n2,i2,h2) h1 == h2 && i1 == i2 && net_equal(n1, n2) +#define NET_FN(n,i,h) h + +#define NET_REHASH mpls_net_rehash +#define NET_PARAMS /8, *2, 2, 2, 8, 24 + + +#define RTA_KEY(fec) fec->rta +#define RTA_NEXT(fec) fec->next_k +#define RTA_EQ(r1,r2) r1 == r2 +#define RTA_FN(r) r->hash_key + +#define RTA_REHASH mpls_rta_rehash +#define RTA_PARAMS /8, *2, 2, 2, 8, 24 + + +#define LABEL_KEY(fec) fec->label +#define LABEL_NEXT(fec) fec->next_l +#define LABEL_EQ(l1,l2) l1 == l2 +#define LABEL_FN(l) u32_hash(l) + +#define LABEL_REHASH mpls_label_rehash +#define LABEL_PARAMS /8, *2, 2, 2, 8, 24 + + +HASH_DEFINE_REHASH_FN(NET, struct mpls_fec) +HASH_DEFINE_REHASH_FN(RTA, struct mpls_fec) +HASH_DEFINE_REHASH_FN(LABEL, struct mpls_fec) + + +static void mpls_withdraw_fec(struct mpls_fec_map *m, struct mpls_fec *fec); +static struct ea_storage * mpls_get_key_attrs(struct mpls_fec_map *m, ea_list *src); + +struct mpls_fec_map * +mpls_fec_map_new(pool *pp, struct channel *C, uint rts) +{ + struct pool *p = rp_new(pp, the_bird_domain.the_bird, "MPLS FEC map"); + struct mpls_fec_map *m = mb_allocz(p, sizeof(struct mpls_fec_map)); + struct mpls_channel *c = (void *) C; + + m->pool = p; + m->channel = C; + + m->domain = c->domain; + mpls_lock_domain(m->domain); + + m->handle = mpls_new_handle(c->domain, c->range); + + /* net_hash and rta_hash are initialized on-demand */ + HASH_INIT(m->label_hash, m->pool, 4); + + m->mpls_rts = rts; + + return m; +} + +void +mpls_fec_map_free(struct mpls_fec_map *m) +{ + /* Free stored rtas */ + if (m->attrs_hash.data) + { + HASH_WALK(m->attrs_hash, next_k, fec) + { + ea_free(fec->rta->l); + fec->rta = NULL; + } + HASH_WALK_END; + } + + /* Free allocated labels */ + HASH_WALK(m->label_hash, next_l, fec) + { + if (fec->policy != MPLS_POLICY_STATIC) + mpls_free_label(m->domain, m->handle, fec->label); + } + HASH_WALK_END; + + mpls_free_handle(m->domain, m->handle); + mpls_unlock_domain(m->domain); + + rfree(m->pool); +} + +static slab * +mpls_slab(struct mpls_fec_map *m, uint type) +{ + ASSERT(type <= NET_VPN6); + int pos = type ? (type - 1) : 0; + + if (!m->slabs[pos]) + m->slabs[pos] = sl_new(m->pool, sizeof(struct mpls_fec) + net_addr_length[pos + 1]); + + return m->slabs[pos]; +} + +struct mpls_fec * +mpls_find_fec_by_label(struct mpls_fec_map *m, u32 label) +{ + return HASH_FIND(m->label_hash, LABEL, label); +} + +struct mpls_fec * +mpls_get_fec_by_label(struct mpls_fec_map *m, u32 label) +{ + struct mpls_fec *fec = HASH_FIND(m->label_hash, LABEL, label); + + if (fec) + return fec; + + fec = sl_allocz(mpls_slab(m, 0)); + + fec->label = label; + fec->policy = MPLS_POLICY_STATIC; + + DBG("New FEC lab %u\n", fec->label); + + HASH_INSERT2(m->label_hash, LABEL, m->pool, fec); + + return fec; +} + +struct mpls_fec * +mpls_get_fec_by_net(struct mpls_fec_map *m, const net_addr *net, u32 path_id) +{ + if (!m->net_hash.data) + HASH_INIT(m->net_hash, m->pool, 4); + + u32 hash = net_hash(net) ^ u32_hash(path_id); + struct mpls_fec *fec = HASH_FIND(m->net_hash, NET, net, path_id, hash); + + if (fec) + return fec; + + fec = sl_allocz(mpls_slab(m, net->type)); + + fec->hash = hash; + fec->path_id = path_id; + net_copy(fec->net, net); + + fec->label = mpls_new_label(m->domain, m->handle); + fec->policy = MPLS_POLICY_PREFIX; + + DBG("New FEC net %u\n", fec->label); + + HASH_INSERT2(m->net_hash, NET, m->pool, fec); + HASH_INSERT2(m->label_hash, LABEL, m->pool, fec); + + return fec; +} + +struct mpls_fec * +mpls_get_fec_by_destination(struct mpls_fec_map *m, ea_list *dest) +{ + if (!m->attrs_hash.data) + HASH_INIT(m->attrs_hash, m->pool, 4); + + struct ea_storage *rta = mpls_get_key_attrs(m, dest); + u32 hash = rta->hash_key; + struct mpls_fec *fec = HASH_FIND(m->attrs_hash, RTA, rta); + + if (fec) + { + ea_free(rta->l); + return fec; + } + + fec = sl_allocz(mpls_slab(m, 0)); + + fec->hash = hash; + fec->rta = rta; + + fec->label = mpls_new_label(m->domain, m->handle); + fec->policy = MPLS_POLICY_AGGREGATE; + + DBG("New FEC rta %u\n", fec->label); + + HASH_INSERT2(m->attrs_hash, RTA, m->pool, fec); + HASH_INSERT2(m->label_hash, LABEL, m->pool, fec); + + return fec; +} + +void +mpls_free_fec(struct mpls_fec_map *m, struct mpls_fec *fec) +{ + if (fec->state != MPLS_FEC_DOWN) + mpls_withdraw_fec(m, fec); + + DBG("Free FEC %u\n", fec->label); + + mpls_free_label(m->domain, m->handle, fec->label); + HASH_REMOVE2(m->label_hash, LABEL, m->pool, fec); + + switch (fec->policy) + { + case MPLS_POLICY_STATIC: + break; + + case MPLS_POLICY_PREFIX: + HASH_REMOVE2(m->net_hash, NET, m->pool, fec); + break; + + case MPLS_POLICY_AGGREGATE: + ea_free(fec->rta->l); + HASH_REMOVE2(m->attrs_hash, RTA, m->pool, fec); + break; + + default: + bug("Unknown fec type"); + } + + sl_free(fec); +} + +static inline void mpls_lock_fec(struct mpls_fec_map *x UNUSED, struct mpls_fec *fec) +{ if (fec) fec->uc++; } + +static inline void mpls_unlock_fec(struct mpls_fec_map *x, struct mpls_fec *fec) +{ if (fec && !--fec->uc) mpls_free_fec(x, fec); } + +struct mpls_fec_tmp_lock { + resource r; + struct mpls_fec_map *m; + struct mpls_fec *fec; +}; + +static void +mpls_fec_tmp_lock_free(resource *r) +{ + struct mpls_fec_tmp_lock *l = SKIP_BACK(struct mpls_fec_tmp_lock, r, r); + mpls_unlock_fec(l->m, l->fec); +} + +static void +mpls_fec_tmp_lock_dump(resource *r, unsigned indent UNUSED) +{ + struct mpls_fec_tmp_lock *l = SKIP_BACK(struct mpls_fec_tmp_lock, r, r); + debug("map=%p fec=%p label=%u", l->m, l->fec, l->fec->label); +} + +static struct resclass mpls_fec_tmp_lock_class = { + .name = "Temporary MPLS FEC Lock", + .size = sizeof(struct mpls_fec_tmp_lock), + .free = mpls_fec_tmp_lock_free, + .dump = mpls_fec_tmp_lock_dump, +}; + +static void +mpls_lock_fec_tmp(struct mpls_fec_map *m, struct mpls_fec *fec) +{ + if (!fec) + return; + + fec->uc++; + + struct mpls_fec_tmp_lock *l = ralloc(tmp_res.pool, &mpls_fec_tmp_lock_class); + l->m = m; + l->fec = fec; +} + +static inline void +mpls_damage_fec(struct mpls_fec_map *m UNUSED, struct mpls_fec *fec) +{ + if (fec->state == MPLS_FEC_CLEAN) + fec->state = MPLS_FEC_DIRTY; +} + +static struct ea_storage * +mpls_get_key_attrs(struct mpls_fec_map *m, ea_list *src) +{ + EA_LOCAL_LIST(4) ea = { + .l.flags = EALF_SORTED, + }; + + uint last_id = 0; + #define PUT_ATTR(cls) do { \ + ASSERT_DIE(last_id < (cls)->id); \ + last_id = (cls)->id; \ + eattr *a = ea_find_by_class(src, (cls)); \ + if (a) ea.a[ea.l.count++] = *a; \ + } while (0) + + PUT_ATTR(&ea_gen_nexthop); + PUT_ATTR(&ea_gen_hostentry); + ea.a[ea.l.count++] = EA_LITERAL_EMBEDDED(&ea_gen_source, 0, m->mpls_rts); + PUT_ATTR(&ea_gen_mpls_class); + + return ea_get_storage(ea_lookup(&ea.l, 0)); +} + +static void +mpls_announce_fec(struct mpls_fec_map *m, struct mpls_fec *fec, ea_list *src) +{ + /* Check existence of hostentry */ + const struct eattr *heea = ea_find_by_class(src, &ea_gen_hostentry); + if (heea) { + /* The same hostentry, but different dependent table */ + struct hostentry_adata *head = SKIP_BACK(struct hostentry_adata, ad, heea->u.ad); + struct hostentry *he = head->he; + ea_set_hostentry(&src, m->channel->table, he->owner, he->addr, he->link, + HOSTENTRY_LABEL_COUNT(head), head->labels); + } + + net_addr_mpls n = NET_ADDR_MPLS(fec->label); + + rte e = { + .src = m->channel->proto->main_source, + .attrs = src, + }; + + fec->state = MPLS_FEC_CLEAN; + rte_update(m->channel, (net_addr *) &n, &e, m->channel->proto->main_source); +} + +static void +mpls_withdraw_fec(struct mpls_fec_map *m, struct mpls_fec *fec) +{ + net_addr_mpls n = NET_ADDR_MPLS(fec->label); + + fec->state = MPLS_FEC_DOWN; + rte_update(m->channel, (net_addr *) &n, NULL, m->channel->proto->main_source); +} + +static void +mpls_apply_fec(rte *r, struct mpls_fec *fec) +{ + ea_set_attr_u32(&r->attrs, &ea_gen_mpls_label, 0, fec->label); + ea_set_attr_u32(&r->attrs, &ea_gen_mpls_policy, 0, fec->policy); +} + + +void +mpls_handle_rte(struct mpls_fec_map *m, const net_addr *n, rte *r) +{ + struct mpls_fec *fec = NULL; + + /* Select FEC for route */ + uint policy = ea_get_int(r->attrs, &ea_gen_mpls_policy, 0); + switch (policy) + { + case MPLS_POLICY_NONE: + return; + + case MPLS_POLICY_STATIC:; + uint label = ea_get_int(r->attrs, &ea_gen_mpls_label, 0); + + if (label < 16) + return; + + fec = mpls_get_fec_by_label(m, label); + mpls_damage_fec(m, fec); + break; + + case MPLS_POLICY_PREFIX: + fec = mpls_get_fec_by_net(m, n, r->src->private_id); + mpls_damage_fec(m, fec); + break; + + case MPLS_POLICY_AGGREGATE: + fec = mpls_get_fec_by_destination(m, r->attrs); + break; + + default: + log(L_WARN "Route %N has invalid MPLS policy %u", n, policy); + return; + } + + /* Temporarily lock FEC */ + mpls_lock_fec_tmp(m, fec); + + /* Apply FEC label to route */ + mpls_apply_fec(r, fec); + + /* Announce MPLS rule for new/updated FEC */ + if (fec->state != MPLS_FEC_CLEAN) + mpls_announce_fec(m, fec, r->attrs); +} + +static inline struct mpls_fec_tmp_lock +mpls_rte_get_fec_lock(const rte *r) +{ + struct mpls_fec_tmp_lock mt = { + .m = SKIP_BACK(struct proto, sources, r->src->owner)->mpls_map, + }; + + if (!mt.m) + return mt; + + uint label = ea_get_int(r->attrs, &ea_gen_mpls_label, 0); + if (label < 16) + return mt; + + mt.fec = mpls_find_fec_by_label(mt.m, label); + return mt; +} + +void +mpls_rte_preimport(rte *new, const rte *old) +{ + struct mpls_fec_tmp_lock new_mt = {}, old_mt = {}; + + if (new) + new_mt = mpls_rte_get_fec_lock(new); + + if (old) + old_mt = mpls_rte_get_fec_lock(old); + + if (new_mt.fec == old_mt.fec) + return; + + if (new_mt.fec) + mpls_lock_fec(new_mt.m, new_mt.fec); + + if (old_mt.fec) + mpls_unlock_fec(old_mt.m, old_mt.fec); +} + +struct ea_class ea_gen_mpls_policy = { + .name = "mpls_policy", + .type = T_ENUM_MPLS_POLICY, +}; + +struct ea_class ea_gen_mpls_class = { + .name = "mpls_class", + .type = T_INT, +}; + +struct ea_class ea_gen_mpls_label = { + .name = "mpls_label", + .type = T_INT, +}; diff --git a/nest/mpls.h b/nest/mpls.h new file mode 100644 index 00000000..2d188d02 --- /dev/null +++ b/nest/mpls.h @@ -0,0 +1,166 @@ +/* + * BIRD Internet Routing Daemon -- MPLS Structures + * + * (c) 2022 Ondrej Zajicek + * (c) 2022 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#ifndef _BIRD_MPLS_H_ +#define _BIRD_MPLS_H_ + +#include "nest/bird.h" +#include "lib/bitmap.h" +#include "lib/hash.h" +#include "nest/route.h" +#include "nest/protocol.h" + + +#define MPLS_POLICY_NONE 0 +#define MPLS_POLICY_STATIC 1 +#define MPLS_POLICY_PREFIX 2 +#define MPLS_POLICY_AGGREGATE 3 + +#define MPLS_FEC_DOWN 0 +#define MPLS_FEC_CLEAN 1 +#define MPLS_FEC_DIRTY 2 + + +struct mpls_domain_config { + node n; /* Node in config.mpls_domains */ + struct mpls_domain *domain; /* Our instance */ + const char *name; + + list ranges; /* List of label ranges (struct mpls_range_config) */ + struct mpls_range_config *static_range; /* Default static label range */ + struct mpls_range_config *dynamic_range; /* Default dynamic label range */ +}; + +struct mpls_domain { + node n; /* Node in global list of MPLS domains (mpls_domains) */ + struct mpls_domain_config *cf; /* Our config */ + const char *name; + pool *pool; /* Pool for the domain and associated objects */ + + struct lmap labels; /* Bitmap of allocated labels */ + uint label_count; /* Number of allocated labels */ + uint use_count; /* Reference counter */ + + struct config *removed; /* Deconfigured, waiting for zero use_count, + while keeping config obstacle */ + + list ranges; /* List of label ranges (struct mpls_range) */ + list handles; /* List of label handles (struct mpls_handle) */ +}; + +struct mpls_range_config { + node n; /* Node in mpls_domain_config.ranges */ + struct mpls_range *range; /* Our instance */ + struct mpls_domain_config *domain; /* Parent MPLS domain */ + const char *name; + + uint start; /* Label range start, (uint) -1 for undefined */ + uint length; /* Label range length, (uint) -1 for undefined */ +}; + +struct mpls_range { + node n; /* Node in mpls_domain.ranges */ + struct mpls_range_config *cf; /* Our config */ + const char *name; + + uint lo, hi; /* Label range interval */ + uint label_count; /* Number of allocated labels */ + uint use_count; /* Reference counter */ + u8 removed; /* Deconfigured, waiting for zero use_count */ +}; + +struct mpls_handle { + node n; /* Node in mpls_domain.handles */ + + struct mpls_range *range; /* Associated range, keeping reference */ + uint label_count; /* Number of allocated labels */ +}; + + +void mpls_init(void); +struct mpls_domain_config * mpls_domain_config_new(struct symbol *s); +void mpls_domain_postconfig(struct mpls_domain_config *cf); +struct mpls_range_config * mpls_range_config_new(struct mpls_domain_config *m, struct symbol *s); +void mpls_preconfig(struct config *c); +void mpls_commit(struct config *new, struct config *old); +uint mpls_new_label(struct mpls_domain *m, struct mpls_handle *h); +void mpls_free_label(struct mpls_domain *m, struct mpls_handle *h, uint n); + +static inline struct mpls_domain_config *cf_default_mpls_domain(struct config *cfg) +{ return EMPTY_LIST(cfg->mpls_domains) ? NULL : HEAD(cfg->mpls_domains); } + + +struct mpls_channel_config { + struct channel_config c; + + struct mpls_domain_config *domain; + struct mpls_range_config *range; + + uint label_policy; +}; + +struct mpls_channel { + struct channel c; + + struct mpls_domain *domain; + struct mpls_range *range; + + uint label_policy; +}; + + +void mpls_channel_postconfig(struct channel_config *CF); +extern struct channel_class channel_mpls; + + +struct mpls_fec { + u32 label; /* Label for FEC */ + u32 hash; /* Hash for primary key (net / rta) */ + u32 uc; /* Number of LSPs for FEC */ + union { /* Extension part of key */ + u32 path_id; /* Source path_id */ + }; + + u8 state; /* FEC state (MPLS_FEC_*) */ + u8 policy; /* Label policy (MPLS_POLICY_*) */ + + struct mpls_fec *next_k; /* Next in mpls_fec.net_hash/rta_hash */ + struct mpls_fec *next_l; /* Next in mpls_fec.label_hash */ + union { /* Primary key */ + struct ea_storage *rta; + net_addr net[0]; + }; +}; + +struct mpls_fec_map { + pool *pool; /* Pool for FEC map */ + slab *slabs[4]; /* Slabs for FEC allocation */ + HASH(struct mpls_fec) net_hash; /* Hash table for MPLS_POLICY_PREFIX FECs */ + HASH(struct mpls_fec) attrs_hash; /* Hash table for MPLS_POLICY_AGGREGATE FECs */ + HASH(struct mpls_fec) label_hash; /* Hash table for FEC lookup by label */ + + struct channel *channel; /* MPLS channel for FEC announcement */ + struct mpls_domain *domain; /* MPLS domain, keeping reference */ + struct mpls_handle *handle; /* Handle for allocation of labels */ + + u8 mpls_rts; /* Source value used for MPLS routes (RTS_*) */ +}; + + +struct mpls_fec_map *mpls_fec_map_new(pool *p, struct channel *c, uint rts); +void mpls_fec_map_free(struct mpls_fec_map *m); +struct mpls_fec *mpls_find_fec_by_label(struct mpls_fec_map *x, u32 label); +struct mpls_fec *mpls_get_fec_by_label(struct mpls_fec_map *m, u32 label); +struct mpls_fec *mpls_get_fec_by_net(struct mpls_fec_map *m, const net_addr *net, u32 path_id); +struct mpls_fec *mpls_get_fec_by_destination(struct mpls_fec_map *m, ea_list *dest); +void mpls_free_fec(struct mpls_fec_map *x, struct mpls_fec *fec); +void mpls_handle_rte(struct mpls_fec_map *m, const net_addr *n, rte *r); +void mpls_rte_preimport(rte *new, const rte *old); + +#endif diff --git a/nest/proto.c b/nest/proto.c index bbf41ab1..9be4daee 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -18,6 +18,7 @@ #include "conf/conf.h" #include "nest/route.h" #include "nest/iface.h" +#include "nest/mpls.h" #include "nest/cli.h" #include "filter/filter.h" #include "filter/f-inst.h" @@ -1283,7 +1284,7 @@ channel_config_new(const struct channel_class *cc, const char *name, uint net_ty if (!net_val_match(net_type, proto->protocol->channel_mask)) cf_error("Unsupported channel type"); - if (proto->net_type && (net_type != proto->net_type)) + if (proto->net_type && (net_type != proto->net_type) && (net_type != NET_MPLS)) cf_error("Different channel type"); tab = rt_get_default_table(new_config, net_type); @@ -1502,6 +1503,71 @@ proto_configure_channel(struct proto *p, struct channel **pc, struct channel_con return 1; } +/** + * proto_setup_mpls_map - automatically setup FEC map for protocol + * @p: affected protocol + * @rts: RTS_* value for generated MPLS routes + * @hooks: whether to update rte_insert / rte_remove hooks + * + * Add, remove or reconfigure MPLS FEC map of the protocol @p, depends on + * whether MPLS channel exists, and setup rte_insert / rte_remove hooks with + * default MPLS handlers. It is a convenience function supposed to be called + * from the protocol start and configure hooks, after reconfiguration of + * channels. For shutdown, use proto_shutdown_mpls_map(). If caller uses its own + * rte_insert / rte_remove hooks, it is possible to disable updating hooks and + * doing that manually. + */ +void +proto_setup_mpls_map(struct proto *p, uint rts) +{ + struct mpls_fec_map *m = p->mpls_map; + struct channel *c = p->mpls_channel; + + if (!m && c) + { + /* + * Note that when called from a protocol start hook, it is called before + * mpls_channel_start(). But FEC map locks MPLS domain internally so it does + * not depend on lock from MPLS channel. + */ + p->mpls_map = mpls_fec_map_new(p->pool, c, rts); + } + else if (m && !c) + { + /* + * Note that for reconfiguration, it is called after the MPLS channel has + * been already removed. But removal of active MPLS channel would trigger + * protocol restart anyways. + */ + mpls_fec_map_free(m); + p->mpls_map = NULL; + } + else if (m && c) + { + // mpls_fec_map_reconfigure(m, c); + } +} + + +/** + * proto_shutdown_mpls_map - automatically shutdown FEC map for protocol + * @p: affected protocol + * @hooks: whether to update rte_insert / rte_remove hooks + * + * Remove MPLS FEC map of the protocol @p during protocol shutdown. + */ +void +proto_shutdown_mpls_map(struct proto *p) +{ + struct mpls_fec_map *m = p->mpls_map; + + if (!m) + return; + + mpls_fec_map_free(m); + p->mpls_map = NULL; +} + static void proto_cleanup(struct proto *p) { @@ -1534,6 +1600,7 @@ proto_loop_stopped(void *ptr) proto_cleanup(p); } + static void proto_event(void *ptr) { diff --git a/nest/protocol.h b/nest/protocol.h index 4831d237..e2e3c749 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -34,6 +34,7 @@ struct channel; struct ea_list; struct eattr; struct symbol; +struct mpls_fec_map; /* @@ -161,6 +162,8 @@ struct proto { struct iface *vrf; /* Related VRF instance, NULL if global */ TLIST_LIST(proto_neigh) neighbors; /* List of neighbor structures */ struct iface_subscription iface_sub; /* Interface notification subscription */ + struct channel *mpls_channel; /* MPLS channel, when used */ + struct mpls_fec_map *mpls_map; /* Maps protocol routes to FECs / labels */ const char *name; /* Name of this instance (== cf->name) */ u32 debug; /* Debugging flags */ @@ -677,12 +680,16 @@ struct channel { struct channel_config *proto_cf_find_channel(struct proto_config *p, uint net_type); static inline struct channel_config *proto_cf_main_channel(struct proto_config *pc) { return proto_cf_find_channel(pc, pc->net_type); } +static inline struct channel_config *proto_cf_mpls_channel(struct proto_config *pc) +{ return proto_cf_find_channel(pc, NET_MPLS); } 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 proto_setup_mpls_map(struct proto *p, uint rts); +void proto_shutdown_mpls_map(struct proto *p); void channel_set_state(struct channel *c, uint state); void channel_schedule_reload(struct channel *c, struct channel_import_request *cir); diff --git a/nest/route.h b/nest/route.h index fe100a36..610bc21e 100644 --- a/nest/route.h +++ b/nest/route.h @@ -7,8 +7,8 @@ * Can be freely distributed and used under the terms of the GNU GPL. */ -#ifndef _BIRD_NEST_RT_H_ -#define _BIRD_NEST_RT_H_ +#ifndef _BIRD_ROUTE_H_ +#define _BIRD_ROUTE_H_ #include "lib/lists.h" #include "lib/bitmap.h" @@ -161,6 +161,7 @@ struct rtable_private { struct tbf rl_pipe; /* Rate limiting token buffer for pipe collisions */ struct f_trie *flowspec_trie; /* Trie for evaluation of flowspec notifications */ + // struct mpls_domain *mpls_domain; /* Label allocator for MPLS */ }; /* The final union private-public rtable structure */ @@ -717,6 +718,8 @@ struct hostentry_adata { u32 labels[0]; }; +#define HOSTENTRY_LABEL_COUNT(head) (head->ad.length + sizeof(struct adata) - sizeof(struct hostentry_adata)) / sizeof(u32) + void ea_set_hostentry(ea_list **to, rtable *dep, rtable *tab, ip_addr gw, ip_addr ll, u32 lnum, u32 labels[lnum]); diff --git a/nest/rt-attr.c b/nest/rt-attr.c index 53111696..32723ded 100644 --- a/nest/rt-attr.c +++ b/nest/rt-attr.c @@ -1715,6 +1715,11 @@ rta_init(void) ea_register_init(&ea_gen_from); ea_register_init(&ea_gen_source); ea_register_init(&ea_gen_flowspec_valid); + + /* MPLS route attributes */ + ea_register_init(&ea_gen_mpls_policy); + ea_register_init(&ea_gen_mpls_class); + ea_register_init(&ea_gen_mpls_label); } /* diff --git a/nest/rt-show.c b/nest/rt-show.c index 5d43317a..00d2aa5c 100644 --- a/nest/rt-show.c +++ b/nest/rt-show.c @@ -41,7 +41,7 @@ rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, int primary ea_list *a = e->attrs; int sync_error = d->tab->kernel ? krt_get_sync_error(d->tab->kernel, e) : 0; void (*get_route_info)(const rte *, byte *buf); - eattr *nhea = net_type_match(e->net, NB_DEST) ? + const eattr *nhea = net_type_match(e->net, NB_DEST) ? ea_find(a, &ea_gen_nexthop) : NULL; struct nexthop_adata *nhad = nhea ? (struct nexthop_adata *) nhea->u.ptr : NULL; int dest = nhad ? (NEXTHOP_IS_REACHABLE(nhad) ? RTD_UNICAST : nhad->dest) : RTD_NONE; @@ -67,7 +67,7 @@ rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, int primary if (d->last_table != d->tab) rt_show_table(d); - eattr *heea; + const eattr *heea; struct hostentry_adata *had = NULL; if (!net_is_flow(e->net) && (dest == RTD_NONE) && (heea = ea_find(a, &ea_gen_hostentry))) had = (struct hostentry_adata *) heea->u.ptr; @@ -96,7 +96,7 @@ static void rt_show_net(struct rt_show_data *d, const net_addr *n, const rte **feed, uint count) { struct cli *c = d->cli; - byte ia[NET_MAX_TEXT_LENGTH+1]; + byte ia[NET_MAX_TEXT_LENGTH+16+1]; struct channel *ec = d->tab->export_channel; /* The Clang static analyzer complains that ec may be NULL. @@ -105,6 +105,7 @@ rt_show_net(struct rt_show_data *d, const net_addr *n, const rte **feed, uint co int first = 1; int first_show = 1; + uint last_label = 0; int pass = 0; for (uint i = 0; i < count; i++) @@ -186,13 +187,21 @@ rt_show_net(struct rt_show_data *d, const net_addr *n, const rte **feed, uint co if (d->stats < 2) { - if (first_show) - net_format(n, ia, sizeof(ia)); + uint label = ea_get_int(e.attrs, &ea_gen_mpls_label, ~0U); + + if (first_show || (last_label != label)) + { + if (!~label) + net_format(n, ia, sizeof(ia)); + else + bsnprintf(ia, sizeof(ia), "%N mpls %d", n, label); + } else ia[0] = 0; rt_show_rte(c, ia, &e, d, !d->tab->prefilter && !i); first_show = 0; + last_label = label; } d->show_counter++; diff --git a/nest/rt-table.c b/nest/rt-table.c index 94576191..e074c0bf 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -96,6 +96,7 @@ #include "nest/route.h" #include "nest/protocol.h" #include "nest/iface.h" +#include "nest/mpls.h" #include "lib/resource.h" #include "lib/event.h" #include "lib/timer.h" @@ -1894,21 +1895,22 @@ channel_preimport(struct rt_import_request *req, rte *new, const rte *old) int new_in = new && !rte_is_filtered(new); int old_in = old && !rte_is_filtered(old); + + int verdict = 1; if (new_in && !old_in) if (CHANNEL_LIMIT_PUSH(c, IN)) if (c->in_keep & RIK_REJECTED) - { new->flags |= REF_FILTERED; - return 1; - } else - return 0; + verdict = 0; if (!new_in && old_in) CHANNEL_LIMIT_POP(c, IN); - return 1; + mpls_rte_preimport(new_in ? new : NULL, old_in ? old : NULL); + + return verdict; } void @@ -1951,6 +1953,9 @@ rte_update(struct channel *c, const net_addr *n, rte *new, struct rte_src *src) new = NULL; } + if (new && c->proto->mpls_map) + mpls_handle_rte(c->proto->mpls_map, n, new); + if (new) if (net_is_flow(n)) rt_flowspec_resolve_rte(new, c); @@ -1963,7 +1968,6 @@ rte_update(struct channel *c, const net_addr *n, rte *new, struct rte_src *src) stats->updates_invalid++; new = NULL; } - } else stats->withdraws_received++; @@ -3471,11 +3475,8 @@ rt_postconfig(struct config *c) void ea_set_hostentry(ea_list **to, rtable *dep, rtable *src, ip_addr gw, ip_addr ll, u32 lnum, u32 labels[lnum]) { - struct { - struct adata ad; - struct hostentry *he; - u32 labels[0]; - } *head = (void *) tmp_alloc_adata(sizeof *head + sizeof(u32) * lnum - sizeof(struct adata)); + struct hostentry_adata *head = (struct hostentry_adata *) tmp_alloc_adata( + sizeof *head + sizeof(u32) * lnum - sizeof(struct adata)); RT_LOCKED(src, tab) head->he = rt_get_hostentry(tab, gw, ll, dep); diff --git a/sysdep/unix/krt.Y b/sysdep/unix/krt.Y index 4ce9a328..92fc1920 100644 --- a/sysdep/unix/krt.Y +++ b/sysdep/unix/krt.Y @@ -33,6 +33,7 @@ CF_KEYWORDS(KERNEL, PERSIST, SCAN, TIME, LEARN, DEVICE, ROUTES, GRACEFUL, RESTAR CF_KEYWORDS(INTERFACE, PREFERRED) %type kern_mp_limit +%type kern_channel CF_GRAMMAR @@ -53,9 +54,15 @@ kern_mp_limit: | LIMIT expr { $$ = $2; if (($2 <= 0) || ($2 > 255)) cf_error("Merge paths limit must be in range 1-255"); } ; + +kern_channel: + proto_channel + | mpls_channel + ; + kern_item: proto_item - | proto_channel { this_proto->net_type = $1->net_type; } + | kern_channel { this_proto->net_type = $1->net_type; } | PERSIST bool { THIS_KRT->persist = $2; } | SCAN TIME expr { /* Scan time of 0 means scan on startup only */ diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c index f0ed85e2..f9ffbd01 100644 --- a/sysdep/unix/main.c +++ b/sysdep/unix/main.c @@ -34,6 +34,7 @@ #include "nest/route.h" #include "nest/protocol.h" #include "nest/iface.h" +#include "nest/mpls.h" #include "nest/cli.h" #include "nest/locks.h" #include "conf/conf.h" @@ -905,6 +906,7 @@ main(int argc, char **argv) rt_init(); io_init(); if_init(); + mpls_init(); // roa_init(); config_init(); diff --git a/test/bt-utils.c b/test/bt-utils.c index 53ee92d2..56124e1f 100644 --- a/test/bt-utils.c +++ b/test/bt-utils.c @@ -16,6 +16,7 @@ #include "nest/bird.h" #include "nest/route.h" #include "nest/protocol.h" +#include "nest/mpls.h" #include "sysdep/unix/unix.h" #include "sysdep/unix/krt.h" @@ -66,6 +67,7 @@ bt_bird_init(void) log_switch(1, NULL, NULL); io_init(); if_init(); + mpls_init(); config_init(); protos_build();