]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
MPLS subsystem
authorOndrej Zajicek <santiago@crfreenet.org>
Wed, 14 Sep 2022 23:38:18 +0000 (01:38 +0200)
committerOndrej Zajicek <santiago@crfreenet.org>
Wed, 4 Oct 2023 11:01:21 +0000 (13:01 +0200)
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.

21 files changed:
conf/cf-lex.l
conf/conf.c
conf/conf.h
conf/confbase.Y
filter/data.h
lib/ip.h
nest/Doc
nest/Makefile
nest/config.Y
nest/mpls.Y [new file with mode: 0644]
nest/mpls.c [new file with mode: 0644]
nest/mpls.h [new file with mode: 0644]
nest/proto.c
nest/protocol.h
nest/route.h
nest/rt-attr.c
nest/rt-show.c
nest/rt-table.c
sysdep/unix/krt.Y
sysdep/unix/main.c
test/bt-utils.c

index 28479ff381b56b372699273ca4b94ed4516dd527..5fb88e03d39578f3efe6d2b16e3c657d2c1cc85c 100644 (file)
@@ -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:
index b9239d9bedff7686e5f711822f5d30fae25dfee1..d98d421cddf0d7ee81bc30d7c0184b8f0fe8451f 100644 (file)
@@ -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;
index b07b417c2f3137eb3e844171d74e87951cc0428a..8fd6713e66181e70dc38040969f98d7e1563a38d 100644 (file)
@@ -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)
index 69a7676c8be829b60a19f45a3ee19e30239be60f..63308290f41d6b91790c5f6bbd8d6fa48b77e3fe 100644 (file)
@@ -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");
index 0a521ec5b28dbeef4a03f80539aaa5ad62f08b69..21a78bf6c16e6a4dd5ba893c6ed3ccffc82f7bb3 100644 (file)
@@ -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 */
index 0e232f97955a2e1e5661b66112776663ec41736b..f2650d3fba3bd8de1d0400dd8ec7e7bab935033e 100644 (file)
--- 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;
index 38af0feb7f55bd62be5be344e20e64aa10b9e28b..3be43a7136f99e2b130ae9e608c655660040134d 100644 (file)
--- 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
index 5a244c7569e33e4be1b3ec903894f9b9405b46b9..ef0fdf6764a746c7dbe1a7e3cdd5951aef941013 100644 (file)
@@ -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 $@
index 82f637835693c25c87b4caeea435f3addd099362..31b9bd44f18da5306c62da8cd4fead371130ed55 100644 (file)
@@ -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 <i32> idval
 %type <f> imexport
@@ -143,7 +146,7 @@ CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6)
 %type <s> optproto
 %type <ra> r_args
 %type <sd> sym_args
-%type <i> 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 <i> 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 <ps> proto_patt proto_patt2
 %type <cc> channel_start proto_channel
 %type <cl> 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 (file)
index 0000000..b4ae990
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ *     BIRD Internet Routing Daemon -- MPLS Structures
+ *
+ *     (c) 2022 Ondrej Zajicek <santiago@crfreenet.org>
+ *     (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 <i> mpls_label_policy
+%type <cc> 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 (file)
index 0000000..cd3a90d
--- /dev/null
@@ -0,0 +1,998 @@
+/*
+ *     BIRD Internet Routing Daemon -- MPLS Structures
+ *
+ *     (c) 2022 Ondrej Zajicek <santiago@crfreenet.org>
+ *     (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 (file)
index 0000000..a84ede1
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ *     BIRD Internet Routing Daemon -- MPLS Structures
+ *
+ *     (c) 2022 Ondrej Zajicek <santiago@crfreenet.org>
+ *     (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
index 48ffade5d2dafead352cb40920d9efeacedcee68..701952fff0f9eddb62bf698c10a131f65b19c5ce 100644 (file)
@@ -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)
index 596e810e7d7bd476bc4827fc26d2e8e488525ee4..81139c33f8b75514267506f80b76df81ca6c9e6b 100644 (file)
@@ -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);
index 4fe43e6ff5bdcfbc81fc1e58d4f7de9dfcbce981..694e3c5d1a8a0f21e242f60c5366329d3daa6820 100644 (file)
@@ -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);
index 9e29abc8f9dcfaf17827917d5efa5fd7b86ca84f..367a08ef483129dc792f23aca3acf00da9f04ab3 100644 (file)
@@ -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;
+
+  case EA_MPLS_LABEL:
+    *buf += bsprintf(*buf, "mpls_label");
+    return GA_NAME;
 
-  return GA_UNKNOWN;
+  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
index 183d023c95808c7ca704b911989697ce55ebfca1..265d5c4472d3d53989ab9a0786b01b0ff511a9c0 100644 (file)
@@ -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++;
index 5e6774651f64467d6678487e2d562e87cd89ee8d..6eaee0699b9c119dbe8446ec00165b456c4dfd6e 100644 (file)
@@ -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;
 
index 95b54d65dae1454bc09fcbbdd7d4bfff3677cd43..5af6a4c8bd03d049eaf1ab982aad74551bb8ca97 100644 (file)
@@ -33,6 +33,7 @@ CF_KEYWORDS(KERNEL, PERSIST, SCAN, TIME, LEARN, DEVICE, ROUTES, GRACEFUL, RESTAR
 CF_KEYWORDS(INTERFACE, PREFERRED)
 
 %type <i> kern_mp_limit
+%type <cc> 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 */
index 23cadb14bb87e9dce8570c9107521078f61b1257..0d7788bb418d872ee5065527e1f4db59fed12914 100644 (file)
@@ -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();
 
index 57b8b824c933672a988d2a24a29d6a2ec27f1c50..a0353a2109192c8dfd7073e87ee354fd4ec5585c 100644 (file)
@@ -66,6 +66,7 @@ bt_bird_init(void)
   io_init();
   rt_init();
   if_init();
+  mpls_init();
   config_init();
 
   protos_build();