summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikael Magnusson <mikma@users.sourceforge.net>2023-11-25 23:38:46 +0100
committerMikael Magnusson <mikma@users.sourceforge.net>2023-11-25 23:38:46 +0100
commitb26801568ed5aa75797d752b1de458cc4bcac152 (patch)
treea10f4d613305fd77d540283952111d9a2f893549
parent03ed8f17dc5c8399ed81bef22e34c62301a3cad1 (diff)
parent333ddd4f981b90d5d3dff166b6abf9bf40bede9f (diff)
Merge commit '333ddd4f' into wireguard-next-tmp7-1
-rw-r--r--conf/cf-lex.l4
-rw-r--r--conf/conf.c4
-rw-r--r--conf/conf.h5
-rw-r--r--conf/confbase.Y2
-rw-r--r--filter/data.h1
-rw-r--r--lib/ip.h2
-rw-r--r--nest/Doc1
-rw-r--r--nest/Makefile3
-rw-r--r--nest/config.Y17
-rw-r--r--nest/mpls.Y141
-rw-r--r--nest/mpls.c998
-rw-r--r--nest/mpls.h170
-rw-r--r--nest/proto.c78
-rw-r--r--nest/protocol.h7
-rw-r--r--nest/route.h8
-rw-r--r--nest/rt-attr.c26
-rw-r--r--nest/rt-show.c15
-rw-r--r--nest/rt-table.c11
-rw-r--r--sysdep/unix/krt.Y9
-rw-r--r--sysdep/unix/main.c2
-rw-r--r--test/bt-utils.c1
21 files changed, 1487 insertions, 18 deletions
diff --git a/conf/cf-lex.l b/conf/cf-lex.l
index 6ac3ab20..c32b1c22 100644
--- a/conf/cf-lex.l
+++ b/conf/cf-lex.l
@@ -915,6 +915,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 811678f2..f48be8e0 100644
--- a/filter/data.h
+++ b/filter/data.h
@@ -43,6 +43,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 b263a2b9..aa75fed7 100644
--- a/nest/Makefile
+++ b/nest/Makefile
@@ -1,7 +1,8 @@
-src := a-path.c a-set.c a-tlv.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 a-tlv.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 <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
index 00000000..b4ae990b
--- /dev/null
+++ b/nest/mpls.Y
@@ -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
index 00000000..cd3a90d9
--- /dev/null
+++ b/nest/mpls.c
@@ -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
index 00000000..a84ede14
--- /dev/null
+++ b/nest/mpls.h
@@ -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
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 7d41dd13..533e1be1 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;
/*
@@ -174,6 +175,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 */
@@ -622,12 +625,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 828ac804..47ba8a9d 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
@@ -688,7 +692,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;
+
+ 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
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 <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 */
diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c
index 683d26e1..1df811e7 100644
--- a/sysdep/unix/main.c
+++ b/sysdep/unix/main.c
@@ -34,6 +34,7 @@
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
+#include "nest/mpls.h"
#include "nest/cli.h"
#include "nest/locks.h"
#include "conf/conf.h"
@@ -899,6 +900,7 @@ main(int argc, char **argv)
io_init();
rt_init();
if_init();
+ mpls_init();
// roa_init();
config_init();
tunnel_encap_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();