From 333ddd4f981b90d5d3dff166b6abf9bf40bede9f Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Thu, 15 Sep 2022 01:38:18 +0200 Subject: [PATCH] MPLS subsystem 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. There was already a preliminary MPLS support consisting of MPLS label net_addr, MPLS routing tables with static MPLS routes, remote labels in next hops, and kernel protocol support. This patch adds the MPLS domain as a basic structure representing local label space with dynamic label allocator and configurable label ranges. To represent LSPs, allocated local labels can be attached as route attributes to IP or VPN routes with local labels as attributes. There are several steps for handling LSP routes in routing protocols -- deciding to which forwarding equivalence class (FEC) the LSP route belongs, allocating labels for new FECs, announcing MPLS routes for new FECs, attaching labels to LSP routes. The FEC map structure implements basic code for managing FECs in routing protocols, therefore existing protocols can be made MPLS-aware by adding FEC map and delegating most work related to local label management to it. --- conf/cf-lex.l | 4 + conf/conf.c | 4 + conf/conf.h | 5 + conf/confbase.Y | 2 + filter/data.h | 1 + lib/ip.h | 2 + nest/Doc | 1 + nest/Makefile | 3 +- nest/config.Y | 17 +- nest/mpls.Y | 141 +++++++ nest/mpls.c | 998 +++++++++++++++++++++++++++++++++++++++++++++ nest/mpls.h | 170 ++++++++ nest/proto.c | 78 +++- nest/protocol.h | 7 + nest/route.h | 8 +- nest/rt-attr.c | 26 +- nest/rt-show.c | 15 +- nest/rt-table.c | 11 +- sysdep/unix/krt.Y | 9 +- sysdep/unix/main.c | 2 + test/bt-utils.c | 1 + 21 files changed, 1487 insertions(+), 18 deletions(-) create mode 100644 nest/mpls.Y create mode 100644 nest/mpls.c create mode 100644 nest/mpls.h diff --git a/conf/cf-lex.l b/conf/cf-lex.l index 28479ff3..5fb88e03 100644 --- a/conf/cf-lex.l +++ b/conf/cf-lex.l @@ -858,6 +858,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_CONSTANT_RANGE: return "constant"; case SYM_VARIABLE_RANGE: diff --git a/conf/conf.c b/conf/conf.c index b9239d9b..d98d421c 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" @@ -139,6 +140,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); @@ -299,6 +301,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"); @@ -547,6 +550,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 b07b417c..8fd6713e 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 */ @@ -128,6 +129,8 @@ struct symbol { const struct filter *filter; /* For SYM_FILTER */ struct rtable_config *table; /* For SYM_TABLE */ struct f_dynamic_attr *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 */ @@ -167,6 +170,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 69a7676c..63308290 100644 --- a/conf/confbase.Y +++ b/conf/confbase.Y @@ -43,6 +43,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/filter/data.h b/filter/data.h index 0a521ec5..21a78bf6 100644 --- a/filter/data.h +++ b/filter/data.h @@ -42,6 +42,7 @@ enum f_type { T_ENUM_NETTYPE = 0x36, T_ENUM_RA_PREFERENCE = 0x37, T_ENUM_AF = 0x38, + T_ENUM_MPLS_POLICY = 0x39, /* new enums go here */ T_ENUM_EMPTY = 0x3f, /* Special hack for atomic_aggr */ diff --git a/lib/ip.h b/lib/ip.h index 0e232f97..f2650d3f 100644 --- a/lib/ip.h +++ b/lib/ip.h @@ -385,6 +385,8 @@ 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 0x100000 + #define MPLS_MAX_LABEL_STACK 8 typedef struct mpls_label_stack { uint len; 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 5a244c75..ef0fdf67 100644 --- a/nest/Makefile +++ b/nest/Makefile @@ -1,7 +1,8 @@ -src := a-path.c a-set.c 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 := a-path.c a-set.c 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 82f63783..31b9bd44 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" @@ -126,6 +127,7 @@ CF_KEYWORDS(GRACEFUL, RESTART, WAIT, MAX, AS) CF_KEYWORDS(MIN, IDLE, RX, TX, INTERVAL, MULTIPLIER, PASSIVE) CF_KEYWORDS(CHECK, LINK) CF_KEYWORDS(SORTED, TRIE, MIN, MAX, 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) @@ -136,6 +138,7 @@ CF_ENUM(T_ENUM_SCOPE, SCOPE_, HOST, LINK, SITE, ORGANIZATION, UNIVERSE, UNDEFINE CF_ENUM(T_ENUM_RTD, RTD_, UNICAST, 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 @@ -143,7 +146,7 @@ CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6) %type optproto %type r_args %type sym_args -%type proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_mode limit_action net_type tos password_algorithm +%type proto_start echo_mask echo_size 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 @@ -188,7 +191,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; } @@ -198,6 +201,10 @@ net_type: | ROA6 { $$ = NET_ROA6; } | FLOW4{ $$ = NET_FLOW4; } | FLOW6{ $$ = NET_FLOW6; } + ; + +net_type: + net_type_base | MPLS { $$ = NET_MPLS; } ; @@ -296,7 +303,7 @@ proto_item: ; -channel_start: net_type +channel_start: net_type_base { $$ = this_channel = channel_config_get(NULL, net_label[$1], $1, this_proto); }; @@ -930,6 +937,10 @@ proto_patt2: dynamic_attr: IGP_METRIC { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_GEN_IGP_METRIC); } ; +dynamic_attr: MPLS_LABEL { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_MPLS_LABEL); } ; +dynamic_attr: MPLS_POLICY { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_ENUM_MPLS_POLICY, EA_MPLS_POLICY); } ; +dynamic_attr: MPLS_CLASS { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_MPLS_CLASS); } ; + CF_CODE diff --git a/nest/mpls.Y b/nest/mpls.Y new file mode 100644 index 00000000..b4ae990b --- /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 symbol_known { cf_assert_symbol($2, SYM_MPLS_DOMAIN); MPLS_CC->domain = $2->mpls_domain; } + | LABEL RANGE symbol_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..cd3a90d9 --- /dev/null +++ b/nest/mpls.c @@ -0,0 +1,998 @@ +/* + * 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, "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, fec->class_id, fec->hash +#define RTA_NEXT(fec) fec->next_k +#define RTA_EQ(r1,i1,h1,r2,i2,h2) h1 == h2 && r1 == r2 && i1 == i2 +#define RTA_FN(r,i,h) h + +#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 rta * mpls_get_key_rta(struct mpls_fec_map *m, const rta *src); + +struct mpls_fec_map * +mpls_fec_map_new(pool *pp, struct channel *C, uint rts) +{ + struct pool *p = rp_new(pp, "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; + m->mpls_scope = SCOPE_UNIVERSE; + + return m; +} + +void +mpls_fec_map_free(struct mpls_fec_map *m) +{ + /* Free stored rtas */ + if (m->rta_hash.data) + { + HASH_WALK(m->rta_hash, next_k, fec) + { + rta_free(fec->rta); + 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_rta(struct mpls_fec_map *m, const rta *src, u32 class_id) +{ + if (!m->rta_hash.data) + HASH_INIT(m->rta_hash, m->pool, 4); + + rta *rta = mpls_get_key_rta(m, src); + u32 hash = rta->hash_key ^ u32_hash(class_id); + struct mpls_fec *fec = HASH_FIND(m->rta_hash, RTA, rta, class_id, hash); + + if (fec) + { + rta_free(rta); + return fec; + } + + fec = sl_allocz(mpls_slab(m, 0)); + + fec->hash = hash; + fec->class_id = class_id; + 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->rta_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: + rta_free(fec->rta); + HASH_REMOVE2(m->rta_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); } + +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 rta * +mpls_get_key_rta(struct mpls_fec_map *m, const rta *src) +{ + rta *a = allocz(RTA_MAX_SIZE); + + a->source = m->mpls_rts; + a->scope = m->mpls_scope; + + if (!src->hostentry) + { + /* Just copy the nexthop */ + a->dest = src->dest; + nexthop_link(a, &src->nh); + } + else + { + /* Keep the hostentry */ + a->hostentry = src->hostentry; + + /* Keep the original labelstack */ + const u32 *labels = &src->nh.label[src->nh.labels - src->nh.labels_orig]; + a->nh.labels = a->nh.labels_orig = src->nh.labels_orig; + memcpy(a->nh.label, labels, src->nh.labels_orig * sizeof(u32)); + } + + return rta_lookup(a); +} + +static void +mpls_announce_fec(struct mpls_fec_map *m, struct mpls_fec *fec, const rta *src) +{ + rta *a = allocz(RTA_MAX_SIZE); + + a->source = m->mpls_rts; + a->scope = m->mpls_scope; + + if (!src->hostentry) + { + /* Just copy the nexthop */ + a->dest = src->dest; + nexthop_link(a, &src->nh); + } + else + { + const u32 *labels = &src->nh.label[src->nh.labels - src->nh.labels_orig]; + mpls_label_stack ms; + + /* Apply the hostentry with the original labelstack */ + ms.len = src->nh.labels_orig; + memcpy(ms.stack, labels, src->nh.labels_orig * sizeof(u32)); + rta_apply_hostentry(a, src->hostentry, &ms); + } + + net_addr_mpls n = NET_ADDR_MPLS(fec->label); + + rte *e = rte_get_temp(rta_lookup(a), m->channel->proto->main_source); + e->pflags = 0; + + fec->state = MPLS_FEC_CLEAN; + rte_update2(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_update2(m->channel, (net_addr *) &n, NULL, m->channel->proto->main_source); +} + +static void +mpls_apply_fec(rte *r, struct mpls_fec *fec, linpool *lp) +{ + struct ea_list *ea = lp_allocz(lp, sizeof(struct ea_list) + 2 * sizeof(eattr)); + + rta *old_attrs = r->attrs; + + if (rta_is_cached(old_attrs)) + r->attrs = rta_do_cow(r->attrs, lp); + + *ea = (struct ea_list) { + .next = r->attrs->eattrs, + .flags = EALF_SORTED, + .count = 2, + }; + + ea->attrs[0] = (struct eattr) { + .id = EA_MPLS_LABEL, + .type = EAF_TYPE_INT, + .u.data = fec->label, + }; + + ea->attrs[1] = (struct eattr) { + .id = EA_MPLS_POLICY, + .type = EAF_TYPE_INT, + .u.data = fec->policy, + }; + + r->attrs->eattrs = ea; + + if (rta_is_cached(old_attrs)) + { + r->attrs = rta_lookup(r->attrs); + rta_free(old_attrs); + } +} + + +void +mpls_handle_rte(struct mpls_fec_map *m, const net_addr *n, rte *r, linpool *lp, struct mpls_fec **locked_fec) +{ + ASSERT(!(r->flags & REF_COW)); + + struct mpls_fec *fec = NULL; + + + /* Select FEC for route */ + uint policy = ea_get_int(r->attrs->eattrs, EA_MPLS_POLICY, 0); + switch (policy) + { + case MPLS_POLICY_NONE: + return; + + case MPLS_POLICY_STATIC:; + uint label = ea_get_int(r->attrs->eattrs, EA_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:; + uint class = ea_get_int(r->attrs->eattrs, EA_MPLS_CLASS, 0); + fec = mpls_get_fec_by_rta(m, r->attrs, class); + break; + + default: + log(L_WARN "Route %N has invalid MPLS policy %u", n, policy); + return; + } + + /* Temporarily lock FEC */ + mpls_lock_fec(m, fec); + *locked_fec = fec; + + /* Apply FEC label to route */ + mpls_apply_fec(r, fec, lp); + + /* Announce MPLS rule for new/updated FEC */ + if (fec->state != MPLS_FEC_CLEAN) + mpls_announce_fec(m, fec, r->attrs); +} + +void +mpls_handle_rte_cleanup(struct mpls_fec_map *m, struct mpls_fec **locked_fec) +{ + /* Unlock temporarily locked FEC from mpls_handle_rte() */ + if (*locked_fec) + { + mpls_unlock_fec(m, *locked_fec); + *locked_fec = NULL; + } +} + +void +mpls_rte_insert(net *n UNUSED, rte *r) +{ + struct proto *p = r->src->proto; + struct mpls_fec_map *m = p->mpls_map; + + uint label = ea_get_int(r->attrs->eattrs, EA_MPLS_LABEL, 0); + if (label < 16) + return; + + struct mpls_fec *fec = mpls_find_fec_by_label(m, label); + if (!fec) + return; + + mpls_lock_fec(m, fec); +} + +void +mpls_rte_remove(net *n UNUSED, rte *r) +{ + struct proto *p = r->src->proto; + struct mpls_fec_map *m = p->mpls_map; + + uint label = ea_get_int(r->attrs->eattrs, EA_MPLS_LABEL, 0); + if (label < 16) + return; + + struct mpls_fec *fec = mpls_find_fec_by_label(m, label); + if (!fec) + return; + + mpls_unlock_fec(m, fec); +} diff --git a/nest/mpls.h b/nest/mpls.h new file mode 100644 index 00000000..a84ede14 --- /dev/null +++ b/nest/mpls.h @@ -0,0 +1,170 @@ +/* + * 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 */ + u32 class_id; /* Aaggregation class */ + }; + + 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 rta *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) rta_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_*) */ + u8 mpls_scope; /* Scope value used for MPLS routes () */ +}; + + +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_rta(struct mpls_fec_map *m, const rta *src, u32 class_id); +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, linpool *lp, struct mpls_fec **locked_fec); +void mpls_handle_rte_cleanup(struct mpls_fec_map *m, struct mpls_fec **locked_fec); +void mpls_rte_insert(net *n UNUSED, rte *r); +void mpls_rte_remove(net *n UNUSED, rte *r); + +#endif diff --git a/nest/proto.c b/nest/proto.c index 48ffade5..701952ff 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" @@ -764,7 +765,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 = new_config->def_tables[net_type]; @@ -955,6 +956,81 @@ 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, int hooks) +{ + 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); + } + + if (hooks) + { + p->rte_insert = p->mpls_map ? mpls_rte_insert : NULL; + p->rte_remove = p->mpls_map ? mpls_rte_remove : NULL; + } +} + +/** + * 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, int hooks) +{ + struct mpls_fec_map *m = p->mpls_map; + + if (!m) + return; + + mpls_fec_map_free(m); + p->mpls_map = NULL; + + if (hooks) + { + p->rte_insert = NULL; + p->rte_remove = NULL; + } +} static void proto_event(void *ptr) diff --git a/nest/protocol.h b/nest/protocol.h index 596e810e..81139c33 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -31,6 +31,7 @@ struct channel; struct ea_list; struct eattr; struct symbol; +struct mpls_fec_map; /* @@ -172,6 +173,8 @@ struct proto { struct channel *main_channel; /* Primary channel */ struct rte_src *main_source; /* Primary route source */ struct iface *vrf; /* Related VRF instance, NULL if global */ + 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 */ @@ -619,12 +622,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, struct 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, int hooks); +void proto_shutdown_mpls_map(struct proto *p, int hooks); void channel_set_state(struct channel *c, uint state); void channel_setup_in_table(struct channel *c); diff --git a/nest/route.h b/nest/route.h index 4fe43e6f..694e3c5d 100644 --- a/nest/route.h +++ b/nest/route.h @@ -200,6 +200,7 @@ typedef struct rtable { struct timer *settle_timer; /* Settle time for notifications */ list flowspec_links; /* List of flowspec links, src for NET_IPx and dst for NET_FLOWx */ struct f_trie *flowspec_trie; /* Trie for evaluation of flowspec notifications */ + // struct mpls_domain *mpls_domain; /* Label allocator for MPLS */ } rtable; struct rt_subscription { @@ -527,7 +528,10 @@ typedef struct eattr { const char *ea_custom_name(uint ea); -#define EA_GEN_IGP_METRIC EA_CODE(PROTOCOL_NONE, 0) +#define EA_GEN_IGP_METRIC EA_CODE(PROTOCOL_NONE, 0) +#define EA_MPLS_LABEL EA_CODE(PROTOCOL_NONE, 1) +#define EA_MPLS_POLICY EA_CODE(PROTOCOL_NONE, 2) +#define EA_MPLS_CLASS EA_CODE(PROTOCOL_NONE, 3) #define EA_CODE_MASK 0xffff #define EA_CUSTOM_BIT 0x8000 @@ -687,7 +691,7 @@ static inline int nexthop_same(struct nexthop *x, struct nexthop *y) { return (x == y) || nexthop__same(x, y); } struct nexthop *nexthop_merge(struct nexthop *x, struct nexthop *y, int rx, int ry, int max, linpool *lp); struct nexthop *nexthop_sort(struct nexthop *x); -static inline void nexthop_link(struct rta *a, struct nexthop *from) +static inline void nexthop_link(struct rta *a, const struct nexthop *from) { memcpy(&a->nh, from, nexthop_size(from)); } void nexthop_insert(struct nexthop **n, struct nexthop *y); int nexthop_is_sorted(struct nexthop *x); diff --git a/nest/rt-attr.c b/nest/rt-attr.c index 9e29abc8..367a08ef 100644 --- a/nest/rt-attr.c +++ b/nest/rt-attr.c @@ -803,13 +803,27 @@ ea_free(ea_list *o) static int get_generic_attr(const eattr *a, byte **buf, int buflen UNUSED) { - if (a->id == EA_GEN_IGP_METRIC) - { - *buf += bsprintf(*buf, "igp_metric"); - return GA_NAME; - } + switch (a->id) + { + case EA_GEN_IGP_METRIC: + *buf += bsprintf(*buf, "igp_metric"); + return GA_NAME; - return GA_UNKNOWN; + case EA_MPLS_LABEL: + *buf += bsprintf(*buf, "mpls_label"); + return GA_NAME; + + case EA_MPLS_POLICY: + *buf += bsprintf(*buf, "mpls_policy"); + return GA_NAME; + + case EA_MPLS_CLASS: + *buf += bsprintf(*buf, "mpls_class"); + return GA_NAME; + + default: + return GA_UNKNOWN; + } } void diff --git a/nest/rt-show.c b/nest/rt-show.c index 183d023c..265d5c44 100644 --- a/nest/rt-show.c +++ b/nest/rt-show.c @@ -103,7 +103,7 @@ static void rt_show_net(struct cli *c, net *n, struct rt_show_data *d) { rte *e, *ee; - 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. @@ -112,6 +112,7 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d) int first = 1; int first_show = 1; + int last_label = 0; int pass = 0; for (e = n->routes; e; e = e->next) @@ -187,13 +188,21 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d) if (d->stats < 2) { - if (first_show) - net_format(n->n.addr, ia, sizeof(ia)); + int label = (int) ea_get_int(e->attrs->eattrs, EA_MPLS_LABEL, (uint) -1); + + if (first_show || (last_label != label)) + { + if (label < 0) + net_format(n->n.addr, ia, sizeof(ia)); + else + bsnprintf(ia, sizeof(ia), "%N mpls %d", n->n.addr, label); + } else ia[0] = 0; rt_show_rte(c, ia, e, d, (e->net->routes == ee)); first_show = 0; + last_label = label; } d->show_counter++; diff --git a/nest/rt-table.c b/nest/rt-table.c index 5e677465..6eaee069 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -98,6 +98,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" @@ -1559,9 +1560,10 @@ rte_update_unlock(void) void rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src) { - // struct proto *p = c->proto; + struct proto *p = c->proto; struct proto_stats *stats = &c->stats; const struct filter *filter = c->in_filter; + struct mpls_fec *fec = NULL; net *nn; ASSERT(c->channel_state == CS_UP); @@ -1610,6 +1612,10 @@ rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src) new->flags |= REF_FILTERED; } } + + if (p->mpls_map) + mpls_handle_rte(p->mpls_map, n, new, rte_update_pool, &fec); + if (!rta_is_cached(new->attrs)) /* Need to copy attributes */ new->attrs = rta_lookup(new->attrs); new->flags |= REF_COW; @@ -1634,6 +1640,9 @@ rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src) /* And recalculate the best route */ rte_recalculate(c, nn, new, src); + if (p->mpls_map) + mpls_handle_rte_cleanup(p->mpls_map, &fec); + rte_update_unlock(); return; diff --git a/sysdep/unix/krt.Y b/sysdep/unix/krt.Y index 95b54d65..5af6a4c8 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 23cadb14..0d7788bb 100644 --- a/sysdep/unix/main.c +++ b/sysdep/unix/main.c @@ -33,6 +33,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" @@ -895,6 +896,7 @@ main(int argc, char **argv) io_init(); rt_init(); if_init(); + mpls_init(); // roa_init(); config_init(); diff --git a/test/bt-utils.c b/test/bt-utils.c index 57b8b824..a0353a21 100644 --- a/test/bt-utils.c +++ b/test/bt-utils.c @@ -66,6 +66,7 @@ bt_bird_init(void) io_init(); rt_init(); if_init(); + mpls_init(); config_init(); protos_build();