]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Merge branch 'mq-aggregator-for-v3' into thread-next
authorMaria Matejka <mq@ucw.cz>
Wed, 8 Nov 2023 20:51:46 +0000 (21:51 +0100)
committerMaria Matejka <mq@ucw.cz>
Wed, 8 Nov 2023 20:51:46 +0000 (21:51 +0100)
20 files changed:
1  2 
conf/cf-lex.l
conf/conf.c
conf/conf.h
conf/confbase.Y
lib/route.h
lib/type.h
nest/Makefile
nest/config.Y
nest/mpls.Y
nest/mpls.c
nest/mpls.h
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

diff --cc conf/cf-lex.l
index 263311c26e56466de5b349aef54dd146e342eb0a,5fb88e03d39578f3efe6d2b16e3c657d2c1cc85c..6d22f64e12b9523d70e14eadd02f30849b156665
@@@ -934,8 -858,10 +934,12 @@@ cf_symbol_class_name(struct symbol *sym
        return "routing table";
      case SYM_ATTRIBUTE:
        return "custom attribute";
+     case SYM_MPLS_DOMAIN:
+       return "MPLS domain";
+     case SYM_MPLS_RANGE:
+       return "MPLS label range";
 +    case SYM_KEYWORD:
 +      return "symbol";
      case SYM_CONSTANT_RANGE:
        return "constant";
      case SYM_VARIABLE_RANGE:
diff --cc conf/conf.c
Simple merge
diff --cc conf/conf.h
index 841d5c1fc798a55799f69ca848882daba5d47512,5ef7216c0ff955dc412ac0d3cced9eac4c7e6f82..691c86686a40603157487e85d01f4a44ba5ffe64
@@@ -132,7 -128,9 +133,9 @@@ struct symbol 
      const struct f_line *function;    /* For SYM_FUNCTION */
      const struct filter *filter;      /* For SYM_FILTER */
      struct rtable_config *table;      /* For SYM_TABLE */
 -    struct f_dynamic_attr *attribute; /* For SYM_ATTRIBUTE */
 +    struct ea_class *attribute;               /* For SYM_ATTRIBUTE */
+     struct mpls_domain_config *mpls_domain;   /* For SYM_MPLS_DOMAIN */
+     struct mpls_range_config *mpls_range;     /* For SYM_MPLS_RANGE */
      struct f_val *val;                        /* For SYM_CONSTANT */
      uint offset;                      /* For SYM_VARIABLE */
      const struct keyword *keyword;    /* For SYM_KEYWORD */
diff --cc conf/confbase.Y
Simple merge
diff --cc lib/route.h
index 067725771c53cea60e961f65c76cb0fba899d965,0000000000000000000000000000000000000000..9fcf20614b624a60e2b2ce24f4cb0a74879eeccf
mode 100644,000000..100644
--- /dev/null
@@@ -1,570 -1,0 +1,576 @@@
 +/*
 + *    BIRD Internet Routing Daemon -- Routing data structures
 + *
 + *    (c) 1998--2000 Martin Mares <mj@ucw.cz>
 + *    (c) 2022 Maria Matejka <mq@jmq.cz>
 + *
 + *    Can be freely distributed and used under the terms of the GNU GPL.
 + */
 +
 +#ifndef _BIRD_LIB_ROUTE_H_
 +#define _BIRD_LIB_ROUTE_H_
 +
 +#undef RT_SOURCE_DEBUG
 +
 +#include "lib/type.h"
 +#include "lib/rcu.h"
 +#include "lib/hash.h"
 +#include "lib/event.h"
 +
 +struct network;
 +struct proto;
 +struct cli;
 +struct rtable_private;
 +struct rte_storage;
 +
 +#define RTE_IN_TABLE_WRITABLE \
 +  byte pflags;                                /* Protocol-specific flags; may change in-table (!) */ \
 +  u8 stale_cycle;                     /* Auxiliary value for route refresh; may change in-table (!) */ \
 +
 +typedef struct rte {
 +  RTE_IN_TABLE_WRITABLE;
 +  byte flags;                         /* Table-specific flags */
 +  u8 generation;                      /* If this route import is based on other previously exported route,
 +                                         this value should be 1 + MAX(generation of the parent routes).
 +                                         Otherwise the route is independent and this value is zero. */
 +  u32 id;                             /* Table specific route id */
 +  struct ea_list *attrs;              /* Attributes of this route */
 +  const net_addr *net;                        /* Network this RTE belongs to */
 +  struct rte_src *src;                        /* Route source that created the route */
 +  struct rt_import_hook *sender;      /* Import hook used to send the route to the routing table */
 +  btime lastmod;                      /* Last modified (set by table) */
 +} rte;
 +
 +#define REF_FILTERED  2               /* Route is rejected by import filter */
 +#define REF_PENDING   32              /* Route has not propagated completely yet */
 +
 +/* Route is valid for propagation (may depend on other flags in the future), accepts NULL */
 +static inline int rte_is_valid(const rte *r) { return r && !(r->flags & REF_FILTERED); }
 +
 +/* Route just has REF_FILTERED flag */
 +static inline int rte_is_filtered(const rte *r) { return !!(r->flags & REF_FILTERED); }
 +
 +/* Strip the route of the table-specific values */
 +static inline rte rte_init_from(const rte *r)
 +{
 +  return (rte) {
 +    .attrs = r->attrs,
 +    .net = r->net,
 +    .src = r->src,
 +  };
 +}
 +
 +int rte_same(const rte *, const rte *);
 +
 +struct rte_src {
 +  struct rte_src *next;                       /* Hash chain */
 +  struct rte_owner *owner;            /* Route source owner */
 +  u64 private_id;                     /* Private ID, assigned by the protocol */
 +  u32 global_id;                      /* Globally unique ID of the source */
 +  _Atomic u64 uc;                     /* Use count */
 +};
 +
 +struct rte_owner_class {
 +  void (*get_route_info)(const rte *, byte *buf); /* Get route information (for `show route' command) */
 +  int (*rte_better)(const rte *, const rte *);
 +  int (*rte_mergable)(const rte *, const rte *);
 +  u32 (*rte_igp_metric)(const rte *);
 +};
 +
 +struct rte_owner {
 +  struct rte_owner_class *class;
 +  int (*rte_recalculate)(struct rtable_private *, struct network *, struct rte_storage *new, struct rte_storage *, struct rte_storage *);
 +  HASH(struct rte_src) hash;
 +  const char *name;
 +  u32 hash_key;
 +  u32 uc;
 +  event_list *list;
 +  event *prune;
 +  event *stop;
 +};
 +
 +DEFINE_DOMAIN(attrs);
 +extern DOMAIN(attrs) attrs_domain;
 +
 +#define RTA_LOCK       LOCK_DOMAIN(attrs, attrs_domain)
 +#define RTA_UNLOCK     UNLOCK_DOMAIN(attrs, attrs_domain)
 +
 +#define RTE_SRC_PU_SHIFT      44
 +#define RTE_SRC_IN_PROGRESS   (1ULL << RTE_SRC_PU_SHIFT)
 +
 +/* Get a route source. This also locks the source, therefore the caller has to
 + * unlock the source after the route has been propagated. */
 +struct rte_src *rt_get_source_o(struct rte_owner *o, u32 id);
 +#define rt_get_source(p, id)  rt_get_source_o(&(p)->sources, (id))
 +
 +struct rte_src *rt_find_source_global(u32 id);
 +
 +#ifdef RT_SOURCE_DEBUG
 +#define rt_lock_source _rt_lock_source_internal
 +#define rt_unlock_source _rt_unlock_source_internal
 +#endif
 +
 +static inline void rt_lock_source(struct rte_src *src)
 +{
 +  /* Locking a source is trivial; somebody already holds it so we just increase
 +   * the use count. Nothing can be freed underneath our hands. */
 +  u64 uc = atomic_fetch_add_explicit(&src->uc, 1, memory_order_acq_rel);
 +  ASSERT_DIE(uc > 0);
 +}
 +
 +static inline void rt_unlock_source(struct rte_src *src)
 +{
 +  /* Unlocking is tricky. We do it lockless so at the same time, the prune
 +   * event may be running, therefore if the unlock gets us to zero, it must be
 +   * the last thing in this routine, otherwise the prune routine may find the
 +   * source's usecount zeroed, freeing it prematurely.
 +   *
 +   * The usecount is split into two parts:
 +   * the top 20 bits are an in-progress indicator
 +   * the bottom 44 bits keep the actual usecount.
 +   *
 +   * Therefore at most 1 million of writers can simultaneously unlock the same
 +   * source, while at most ~17T different routes can reference it. Both limits
 +   * are insanely high from the 2022 point of view. Let's suppose that when 17T
 +   * routes or 1M writers get real, we get also 128bit atomic variables in the
 +   * C norm. */
 +
 +  /* First, we push the in-progress indicator */
 +  u64 uc = atomic_fetch_add_explicit(&src->uc, RTE_SRC_IN_PROGRESS, memory_order_acq_rel);
 +
 +  /* Then we split the indicator to its parts. Remember, we got the value before the operation happened. */
 +  u64 pending = (uc >> RTE_SRC_PU_SHIFT) + 1;
 +  uc &= RTE_SRC_IN_PROGRESS - 1;
 +
 +  /* We per-use the RCU critical section indicator to make the prune event wait
 +   * until we finish here in the rare case we get preempted. */
 +  rcu_read_lock();
 +
 +  /* Obviously, there can't be more pending unlocks than the usecount itself */
 +  if (uc == pending)
 +    /* If we're the last unlocker, schedule the owner's prune event */
 +    ev_send(src->owner->list, src->owner->prune);
 +  else
 +    ASSERT_DIE(uc > pending);
 +
 +  /* And now, finally, simultaneously pop the in-progress indicator and the
 +   * usecount, possibly allowing the source pruning routine to free this structure */
 +  atomic_fetch_sub_explicit(&src->uc, RTE_SRC_IN_PROGRESS + 1, memory_order_acq_rel);
 +
 +  /* ... and to reduce the load a bit, the source pruning routine will better wait for
 +   * RCU synchronization instead of a busy loop. */
 +  rcu_read_unlock();
 +}
 +
 +#ifdef RT_SOURCE_DEBUG
 +#undef rt_lock_source
 +#undef rt_unlock_source
 +
 +#define rt_lock_source(x) ( log(L_INFO "Lock source %uG at %s:%d", (x)->global_id, __FILE__, __LINE__), _rt_lock_source_internal(x) )
 +#define rt_unlock_source(x) ( log(L_INFO "Unlock source %uG at %s:%d", (x)->global_id, __FILE__, __LINE__), _rt_unlock_source_internal(x) )
 +#endif
 +
 +void rt_init_sources(struct rte_owner *, const char *name, event_list *list);
 +void rt_destroy_sources(struct rte_owner *, event *);
 +
 +void rt_dump_sources(struct rte_owner *);
 +
 +/*
 + *    Route Attributes
 + *
 + *    Beware: All standard BGP attributes must be represented here instead
 + *    of making them local to the route. This is needed to ensure proper
 + *    construction of BGP route attribute lists.
 + */
 +
 +/* Nexthop structure */
 +struct nexthop {
 +  ip_addr gw;                         /* Next hop */
 +  struct iface *iface;                        /* Outgoing interface */
 +  byte flags;
 +  byte weight;
 +  byte labels;                                /* Number of all labels */
 +  u32 label[0];
 +};
 +
 +/* For packing one into eattrs */
 +struct nexthop_adata {
 +  struct adata ad;
 +  /* There is either a set of nexthops or a special destination (RTD_*) */
 +  union {
 +    struct nexthop nh;
 +    uint dest;
 +  };
 +};
 +
 +#define NEXTHOP_DEST_SIZE     (OFFSETOF(struct nexthop_adata, dest) + sizeof(uint) - OFFSETOF(struct adata, data))
 +#define NEXTHOP_DEST_LITERAL(x)       ((struct nexthop_adata) { \
 +      .ad.length = NEXTHOP_DEST_SIZE, .dest = (x), })
 +
 +#define RNF_ONLINK            0x1     /* Gateway is onlink regardless of IP ranges */
 +
 +
 +#define RTS_STATIC 1                  /* Normal static route */
 +#define RTS_INHERIT 2                 /* Route inherited from kernel */
 +#define RTS_DEVICE 3                  /* Device route */
 +#define RTS_STATIC_DEVICE 4           /* Static device route */
 +#define RTS_REDIRECT 5                        /* Learned via redirect */
 +#define RTS_RIP 6                     /* RIP route */
 +#define RTS_OSPF 7                    /* OSPF route */
 +#define RTS_OSPF_IA 8                 /* OSPF inter-area route */
 +#define RTS_OSPF_EXT1 9                       /* OSPF external route type 1 */
 +#define RTS_OSPF_EXT2 10              /* OSPF external route type 2 */
 +#define RTS_BGP 11                    /* BGP route */
 +#define RTS_PIPE 12                   /* Inter-table wormhole */
 +#define RTS_BABEL 13                  /* Babel route */
 +#define RTS_RPKI 14                   /* Route Origin Authorization */
 +#define RTS_PERF 15                   /* Perf checker */
 +#define RTS_AGGREGATED 16             /* Aggregated route */
 +#define RTS_MAX 17
 +
 +#define RTD_NONE 0                    /* Undefined next hop */
 +#define RTD_UNICAST 1                 /* A standard next hop */
 +#define RTD_BLACKHOLE 2                       /* Silently drop packets */
 +#define RTD_UNREACHABLE 3             /* Reject as unreachable */
 +#define RTD_PROHIBIT 4                        /* Administratively prohibited */
 +#define RTD_MAX 5
 +
 +extern const char * rta_dest_names[RTD_MAX];
 +
 +static inline const char *rta_dest_name(uint n)
 +{ return (n < RTD_MAX) ? rta_dest_names[n] : "???"; }
 +
 +
 +/*
 + *    Extended Route Attributes
 + */
 +
 +typedef struct eattr {
 +  word id;                            /* EA_CODE(PROTOCOL_..., protocol-dependent ID) */
 +  byte flags;                         /* Protocol-dependent flags */
 +  byte type;                          /* Attribute type */
 +  byte rfu:5;
 +  byte originated:1;                  /* The attribute has originated locally */
 +  byte fresh:1;                               /* An uncached attribute (e.g. modified in export filter) */
 +  byte undef:1;                               /* Explicitly undefined */
 +
 +  PADDING(unused, 3, 3);
 +
 +  union bval u;
 +} eattr;
 +
 +
 +#define EA_CODE_MASK 0xffff
 +#define EA_ALLOW_UNDEF 0x10000                /* ea_find: allow EAF_TYPE_UNDEF */
 +#define EA_BIT(n) ((n) << 24)         /* Used in bitfield accessors */
 +#define EA_BIT_GET(ea) ((ea) >> 24)
 +
 +typedef struct ea_list {
 +  struct ea_list *next;                       /* In case we have an override list */
 +  byte flags;                         /* Flags: EALF_... */
 +  byte rfu;
 +  word count;                         /* Number of attributes */
 +  eattr attrs[0];                     /* Attribute definitions themselves */
 +} ea_list;
 +
 +struct ea_storage {
 +  struct ea_storage *next_hash;               /* Next in hash chain */
 +  struct ea_storage **pprev_hash;     /* Previous in hash chain */
 +  _Atomic u32 uc;                     /* Use count */
 +  u32 hash_key;                               /* List hash */
 +  ea_list l[0];                               /* The list itself */
 +};
 +
 +#define EALF_SORTED 1                 /* Attributes are sorted by code */
 +#define EALF_BISECT 2                 /* Use interval bisection for searching */
 +#define EALF_CACHED 4                 /* List is cached */
 +#define EALF_HUGE   8                 /* List is too big to fit into slab */
 +
 +struct ea_class {
 +#define EA_CLASS_INSIDE \
 +  const char *name;                   /* Name (both print and filter) */ \
 +  struct symbol *sym;                 /* Symbol to export to configs */ \
 +  uint id;                            /* Autoassigned attribute ID */ \
 +  uint uc;                            /* Reference count */ \
 +  btype type;                         /* Data type ID */ \
 +  uint readonly:1;                    /* This attribute can't be changed by filters */ \
 +  uint conf:1;                                /* Requested by config */ \
 +  uint hidden:1;                      /* Technical attribute, do not show, do not expose to filters */ \
 +  void (*format)(const eattr *ea, byte *buf, uint size); \
 +  void (*stored)(const eattr *ea);    /* When stored into global hash */ \
 +  void (*freed)(const eattr *ea);     /* When released from global hash */ \
 +
 +  EA_CLASS_INSIDE;
 +};
 +
 +struct ea_class_ref {
 +  resource r;
 +  struct ea_class *class;
 +};
 +
 +void ea_register_init(struct ea_class *);
 +struct ea_class_ref *ea_register_alloc(pool *, struct ea_class);
 +struct ea_class_ref *ea_ref_class(pool *, struct ea_class *); /* Reference for an attribute alias */
 +
 +#define EA_REGISTER_ALL_HELPER(x)     ea_register_init(x);
 +#define EA_REGISTER_ALL(...)          MACRO_FOREACH(EA_REGISTER_ALL_HELPER, __VA_ARGS__)
 +
 +struct ea_class *ea_class_find_by_id(uint id);
 +struct ea_class *ea_class_find_by_name(const char *name);
 +static inline struct ea_class *ea_class_self(struct ea_class *self) { return self; }
 +#define ea_class_find(_arg)   _Generic((_arg), \
 +  uint: ea_class_find_by_id, \
 +  word: ea_class_find_by_id, \
 +  char *: ea_class_find_by_name, \
 +  const char *: ea_class_find_by_name, \
 +  struct ea_class *: ea_class_self)(_arg)
 +
 +struct ea_walk_state {
 +  ea_list *eattrs;                    /* Ccurrent ea_list, initially set by caller */
 +  eattr *ea;                          /* Current eattr, initially NULL */
 +  u32 visited[4];                     /* Bitfield, limiting max to 128 */
 +};
 +
 +#define ea_find(_l, _arg)     _Generic((_arg), uint: ea_find_by_id, struct ea_class *: ea_find_by_class, char *: ea_find_by_name)(_l, _arg)
 +eattr *ea_find_by_id(ea_list *, unsigned ea);
 +static inline eattr *ea_find_by_class(ea_list *l, const struct ea_class *def)
 +{ return ea_find_by_id(l, def->id); }
 +static inline eattr *ea_find_by_name(ea_list *l, const char *name)
 +{
 +  const struct ea_class *def = ea_class_find_by_name(name);
 +  return def ? ea_find_by_class(l, def) : NULL;
 +}
 +
 +#define ea_get_int(_l, _ident, _def)  ({ \
 +    struct ea_class *cls = ea_class_find((_ident)); \
 +    ASSERT_DIE(cls->type & EAF_EMBEDDED); \
 +    const eattr *ea = ea_find((_l), cls->id); \
 +    (ea ? ea->u.data : (_def)); \
 +    })
 +
 +#define ea_get_ip(_l, _ident, _def)  ({ \
 +    struct ea_class *cls = ea_class_find((_ident)); \
 +    ASSERT_DIE(cls->type == T_IP); \
 +    const eattr *ea = ea_find((_l), cls->id); \
 +    (ea ? *((const ip_addr *) ea->u.ptr->data) : (_def)); \
 +    })
 +
 +eattr *ea_walk(struct ea_walk_state *s, uint id, uint max);
 +void ea_dump(ea_list *);
 +int ea_same(ea_list *x, ea_list *y);  /* Test whether two ea_lists are identical */
 +uint ea_hash(ea_list *e);     /* Calculate 16-bit hash value */
 +ea_list *ea_append(ea_list *to, ea_list *what);
 +void ea_format_bitfield(const struct eattr *a, byte *buf, int bufsize, const char **names, int min, int max);
 +
 +/* Normalize ea_list; allocates the result from tmp_linpool */
 +ea_list *ea_normalize(ea_list *e, int overlay);
 +
 +uint ea_list_size(ea_list *);
 +void ea_list_copy(ea_list *dest, ea_list *src, uint size);
 +
 +#define EA_LOCAL_LIST(N)  struct { ea_list l; eattr a[N]; }
 +
 +#define EA_LITERAL_EMBEDDED(_class, _flags, _val) ({ \
 +    btype _type = (_class)->type; \
 +    ASSERT_DIE(_type & EAF_EMBEDDED); \
 +    EA_LITERAL_GENERIC((_class)->id, _type, _flags, .u.i = _val); \
 +    })
 +
 +#define EA_LITERAL_STORE_ADATA(_class, _flags, _buf, _len) ({ \
 +    btype _type = (_class)->type; \
 +    ASSERT_DIE(!(_type & EAF_EMBEDDED)); \
 +    EA_LITERAL_GENERIC((_class)->id, _type, _flags, .u.ad = tmp_store_adata((_buf), (_len))); \
 +    })
 +
 +#define EA_LITERAL_DIRECT_ADATA(_class, _flags, _adata) ({ \
 +    btype _type = (_class)->type; \
 +    ASSERT_DIE(!(_type & EAF_EMBEDDED)); \
 +    EA_LITERAL_GENERIC((_class)->id, _type, _flags, .u.ad = _adata); \
 +    })
 +
 +#define EA_LITERAL_GENERIC(_id, _type, _flags, ...) \
 +  ((eattr) { .id = _id, .type = _type, .flags = _flags, __VA_ARGS__ })
 +
 +static inline eattr *
 +ea_set_attr(ea_list **to, eattr a)
 +{
 +  EA_LOCAL_LIST(1) *ea = tmp_alloc(sizeof(*ea));
 +  *ea = (typeof(*ea)) {
 +    .l.flags = EALF_SORTED,
 +    .l.count = 1,
 +    .l.next = *to,
 +    .a[0] = a,
 +  };
 +
 +  *to = &ea->l;
 +  return &ea->a[0];
 +}
 +
 +static inline void
 +ea_unset_attr(ea_list **to, _Bool local, const struct ea_class *def)
 +{
 +  ea_set_attr(to, EA_LITERAL_GENERIC(def->id, 0, 0,
 +      .fresh = local, .originated = local, .undef = 1));
 +}
 +
 +static inline void
 +ea_set_attr_u32(ea_list **to, const struct ea_class *def, uint flags, u64 data)
 +{ ea_set_attr(to, EA_LITERAL_EMBEDDED(def, flags, data)); }
 +
 +static inline void
 +ea_set_attr_data(ea_list **to, const struct ea_class *def, uint flags, const void *data, uint len)
 +{ ea_set_attr(to, EA_LITERAL_STORE_ADATA(def, flags, data, len)); }
 +
 +static inline void
 +ea_copy_attr(ea_list **to, ea_list *from, const struct ea_class *def)
 +{
 +  eattr *e = ea_find_by_class(from, def);
 +  if (e)
 +    if (e->type & EAF_EMBEDDED)
 +      ea_set_attr_u32(to, def, e->flags, e->u.data);
 +    else
 +      ea_set_attr_data(to, def, e->flags, e->u.ptr->data, e->u.ptr->length);
 +  else
 +    ea_unset_attr(to, 0, def);
 +}
 +
 +/*
 + *    Common route attributes
 + */
 +
 +/* Preference: first-order comparison */
 +extern struct ea_class ea_gen_preference;
 +static inline u32 rt_get_preference(const rte *rt)
 +{ return ea_get_int(rt->attrs, &ea_gen_preference, 0); }
 +
 +/* IGP metric: second-order comparison */
 +extern struct ea_class ea_gen_igp_metric;
 +u32 rt_get_igp_metric(const rte *rt);
 +#define IGP_METRIC_UNKNOWN 0x80000000 /* Default igp_metric used when no other
 +                                         protocol-specific metric is availabe */
 +
 +/* From: Advertising router */
 +extern struct ea_class ea_gen_from;
 +
++
++/* MPLS Label, Policy and Class */
++extern struct ea_class ea_gen_mpls_label,
++       ea_gen_mpls_policy, ea_gen_mpls_class;
++
++
 +/* Source: An old method to devise the route source protocol and kind.
 + * To be superseded in a near future by something more informative. */
 +extern struct ea_class ea_gen_source;
 +static inline u32 rt_get_source_attr(const rte *rt)
 +{ return ea_get_int(rt->attrs, &ea_gen_source, 0); }
 +
 +/* Flowspec validation result */
 +enum flowspec_valid {
 +  FLOWSPEC_UNKNOWN    = 0,
 +  FLOWSPEC_VALID      = 1,
 +  FLOWSPEC_INVALID    = 2,
 +  FLOWSPEC__MAX,
 +};
 +
 +extern const char * flowspec_valid_names[FLOWSPEC__MAX];
 +static inline const char *flowspec_valid_name(enum flowspec_valid v)
 +{ return (v < FLOWSPEC__MAX) ? flowspec_valid_names[v] : "???"; }
 +
 +extern struct ea_class ea_gen_flowspec_valid;
 +static inline enum flowspec_valid rt_get_flowspec_valid(const rte *rt)
 +{ return ea_get_int(rt->attrs, &ea_gen_flowspec_valid, FLOWSPEC_UNKNOWN); }
 +
 +/* Next hop: For now, stored as adata */
 +extern struct ea_class ea_gen_nexthop;
 +
 +static inline void ea_set_dest(struct ea_list **to, uint flags, uint dest)
 +{
 +  struct nexthop_adata nhad = NEXTHOP_DEST_LITERAL(dest);
 +  ea_set_attr_data(to, &ea_gen_nexthop, flags, &nhad.ad.data, nhad.ad.length);
 +}
 +
 +/* Next hop structures */
 +
 +#define NEXTHOP_ALIGNMENT     (_Alignof(struct nexthop))
 +#define NEXTHOP_MAX_SIZE      (sizeof(struct nexthop) + sizeof(u32)*MPLS_MAX_LABEL_STACK)
 +#define NEXTHOP_SIZE(_nh)     NEXTHOP_SIZE_CNT(((_nh)->labels))
 +#define NEXTHOP_SIZE_CNT(cnt) BIRD_ALIGN((sizeof(struct nexthop) + sizeof(u32) * (cnt)), NEXTHOP_ALIGNMENT)
 +#define nexthop_size(nh)      NEXTHOP_SIZE((nh))
 +
 +#define NEXTHOP_NEXT(_nh)     ((void *) (_nh) + NEXTHOP_SIZE(_nh))
 +#define NEXTHOP_END(_nhad)    ((_nhad)->ad.data + (_nhad)->ad.length)
 +#define NEXTHOP_VALID(_nh, _nhad) ((void *) (_nh) < (void *) NEXTHOP_END(_nhad))
 +#define NEXTHOP_ONE(_nhad)    (NEXTHOP_NEXT(&(_nhad)->nh) == NEXTHOP_END(_nhad))
 +
 +#define NEXTHOP_WALK(_iter, _nhad) for ( \
 +    struct nexthop *_iter = &(_nhad)->nh; \
 +    (void *) _iter < (void *) NEXTHOP_END(_nhad); \
 +    _iter = NEXTHOP_NEXT(_iter))
 +
 +
 +static inline int nexthop_same(struct nexthop_adata *x, struct nexthop_adata *y)
 +{ return adata_same(&x->ad, &y->ad); }
 +struct nexthop_adata *nexthop_merge(struct nexthop_adata *x, struct nexthop_adata *y, int max, linpool *lp);
 +struct nexthop_adata *nexthop_sort(struct nexthop_adata *x, linpool *lp);
 +int nexthop_is_sorted(struct nexthop_adata *x);
 +
 +#define NEXTHOP_IS_REACHABLE(nhad)    ((nhad)->ad.length > NEXTHOP_DEST_SIZE)
 +
 +/* Route has regular, reachable nexthop (i.e. not RTD_UNREACHABLE and like) */
 +static inline int rte_is_reachable(rte *r)
 +{
 +  eattr *nhea = ea_find(r->attrs, &ea_gen_nexthop);
 +  if (!nhea)
 +    return 0;
 +
 +  struct nexthop_adata *nhad = (void *) nhea->u.ptr;
 +  return NEXTHOP_IS_REACHABLE(nhad);
 +}
 +
 +static inline int nhea_dest(eattr *nhea)
 +{
 +  if (!nhea)
 +    return RTD_NONE;
 +
 +  struct nexthop_adata *nhad = nhea ? (struct nexthop_adata *) nhea->u.ptr : NULL;
 +  if (NEXTHOP_IS_REACHABLE(nhad))
 +    return RTD_UNICAST;
 +  else
 +    return nhad->dest;
 +}
 +
 +static inline int rte_dest(const rte *r)
 +{
 +  return nhea_dest(ea_find(r->attrs, &ea_gen_nexthop));
 +}
 +
 +void rta_init(void);
 +ea_list *ea_lookup(ea_list *, int overlay);           /* Get a cached (and normalized) variant of this attribute list */
 +static inline int ea_is_cached(const ea_list *r) { return r->flags & EALF_CACHED; }
 +static inline struct ea_storage *ea_get_storage(ea_list *r)
 +{
 +  ASSERT_DIE(ea_is_cached(r));
 +  return SKIP_BACK(struct ea_storage, l[0], r);
 +}
 +
 +static inline ea_list *ea_clone(ea_list *r) {
 +  ASSERT_DIE(0 < atomic_fetch_add_explicit(&ea_get_storage(r)->uc, 1, memory_order_acq_rel));
 +  return r;
 +}
 +void ea__free(struct ea_storage *r);
 +static inline void ea_free(ea_list *l) {
 +  if (!l) return;
 +  struct ea_storage *r = ea_get_storage(l);
 +  if (1 == atomic_fetch_sub_explicit(&r->uc, 1, memory_order_acq_rel)) ea__free(r);
 +}
 +
 +void ea_dump(ea_list *);
 +void ea_dump_all(void);
 +void ea_show_list(struct cli *, ea_list *);
 +
 +#define rta_lookup    ea_lookup
 +#define rta_is_cached ea_is_cached
 +#define rta_clone     ea_clone
 +#define rta_free      ea_free
 +
 +#endif
diff --cc lib/type.h
index 32f16382b91a728346cb286bb97727d01936e16f,0000000000000000000000000000000000000000..e07c98ec04ab62815aa1a717a4372d69c980dd5a
mode 100644,000000..100644
--- /dev/null
@@@ -1,122 -1,0 +1,123 @@@
 +/*
 + *     BIRD Internet Routing Daemon -- Internal Data Types
 + *
 + *     (c) 2022 Maria Matejka <mq@jmq.cz>
 + *
 + *     Can be freely distributed and used under the terms of the GNU GPL.
 + */
 +
 +#ifndef _BIRD_TYPE_H_
 +#define _BIRD_TYPE_H_
 +
 +#include "lib/birdlib.h"
 +#include "lib/attrs.h"
 +
 +union bval {
 +#define BVAL_ITEMS                                                                    \
 +  struct {                                                                            \
 +    u32 data;                 /* Integer type inherited from eattrs */                \
 +    PADDING(data, 0, 4);      /* Must be padded on 64-bits */                         \
 +  };                                                                                  \
 +  struct {                                                                            \
 +    u32 i;                    /* Integer type inherited from filters */               \
 +    PADDING(i, 0, 4);         /* Must be padded on 64-bits */                         \
 +  };                                                                                  \
 +  const struct adata *ptr;    /* Generic attribute data inherited from eattrs */      \
 +  const struct adata *ad;             /* Generic attribute data inherited from filters */     \
 +
 +  BVAL_ITEMS;
 +};
 +
 +union bval_long {
 +  union bval bval;            /* For direct assignments */
 +  BVAL_ITEMS;                 /* For item-wise access */
 +
 +  u64 ec;
 +  lcomm lc;
 +  ip_addr ip;
 +  const net_addr *net;
 +  const char *s;
 +  const struct adata *bs;
 +  const struct f_tree *t;
 +  const struct f_trie *ti;
 +  const struct f_path_mask *path_mask;
 +  struct f_path_mask_item pmi;
 +  struct rte *rte;
 +  struct rte_block {
 +    struct rte **rte;
 +    uint len;
 +  } rte_block;
 +};
 +
 +
 +/* Internal types */
 +enum btype {
 +/* Nothing. Simply nothing. */
 +  T_VOID = 0,
 +  T_NONE = 0xff,
 +
 +/* Something but inaccessible. */
 +  T_OPAQUE = 0x02,            /* Opaque byte string (not filterable) */
 +  T_IFACE = 0x0c,             /* Pointer to an interface (inside adata) */
 +  T_ROUTES_BLOCK = 0x68,      /* Block of route pointers */
 +  T_ROUTE = 0x6a,             /* One route pointer */
 +  T_NEXTHOP_LIST = 0x6c,      /* The whole nexthop block */
 +  T_HOSTENTRY = 0x6e,         /* Hostentry with possible MPLS labels */
 +
 +/* Types shared with eattrs */
 +  T_INT = 0x01,                       /* 32-bit unsigned integer number */
 +  T_IP = 0x04,                        /* IP address */
 +  T_QUAD = 0x05,              /* Router ID (IPv4 address) */
 +  T_PATH = 0x06,              /* BGP AS path (encoding per RFC 1771:4.3) */
 +  T_CLIST = 0x0a,             /* Set of u32's (e.g., a community list) */
 +  T_ECLIST = 0x0e,            /* Set of pairs of u32's - ext. community list */
 +  T_LCLIST = 0x08,            /* Set of triplets of u32's - large community list */
 +
 +  T_ENUM_BGP_ORIGIN = 0x11,   /* BGP Origin enum */
 +  T_ENUM_RA_PREFERENCE = 0x13,        /* RA Preference enum */
 +  T_ENUM_FLOWSPEC_VALID = 0x15,       /* Flowspec validation result */
 +
 +#define EAF_TYPE__MAX 0x1f
 +#define EAF_EMBEDDED 0x01             /* Data stored in eattr.u.data (part of type spec) */
 +                                      /* Otherwise, attribute data is adata */
 +
 +/* Other user visible types which fit in int */
 +  T_BOOL = 0xa0,
 +  T_PAIR = 0xa4,  /*  Notice that pair is stored as integer: first << 16 | second */
 +
 +/* Put enumerational types in 0x20..0x3f range */
 +  T_ENUM_LO = 0x10,
 +  T_ENUM_HI = 0x3f,
 +
 +  T_ENUM_RTS = 0x31,
 +  T_ENUM_SCOPE = 0x33,
++  T_ENUM_MPLS_POLICY = 0x35,
 +  T_ENUM_RTD = 0x37,
 +  T_ENUM_ROA = 0x39,
 +  T_ENUM_NETTYPE = 0x3b,
 +  T_ENUM_AF = 0x3d,
 +
 +/* new enums go here */
 +
 +#define T_ENUM T_ENUM_LO ... T_ENUM_HI
 +
 +/* Bigger ones */
 +  T_NET = 0xb0,
 +  T_STRING = 0xb4,
 +  T_PATH_MASK = 0xb8, /* mask for BGP path */
 +  T_EC = 0xbc,                /* Extended community value, u64 */
 +  T_LC = 0xc0,                /* Large community value, lcomm */
 +  T_RD = 0xc4,                /* Route distinguisher for VPN addresses */
 +  T_PATH_MASK_ITEM = 0xc8,    /* Path mask item for path mask constructors */
 +  T_BYTESTRING = 0xcc,
 +
 +  T_SET = 0x80,
 +  T_PREFIX_SET = 0x84,
 +} PACKED;
 +
 +typedef enum btype btype;
 +
 +STATIC_ASSERT(sizeof(btype) == sizeof(byte));
 +
 +
 +#endif
diff --cc nest/Makefile
index bb141f5c373f25bdf7e8f7c4375c3dcccac244d1,ef0fdf6764a746c7dbe1a7e3cdd5951aef941013..b75992452ad04aefb32fdeb5007b2234bb73be30
@@@ -1,4 -1,4 +1,4 @@@
- src := cli.c cmds.c iface.c locks.c neighbor.c password.c proto.c proto-build.c rt-attr.c rt-dev.c rt-fib.c rt-show.c rt-table.c
 -src := 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
++src := cli.c cmds.c iface.c locks.c mpls.c neighbor.c password.c proto.c proto-build.c rt-attr.c rt-dev.c rt-fib.c rt-show.c rt-table.c
  obj := $(src-o-files)
  $(all-daemon)
  $(cf-local)
diff --cc nest/config.Y
index 8c06126e58bb163958ce76864a7cb3b2889b1106,31b9bd44f18da5306c62da8cd4fead371130ed55..f212e7113dcb3e407bb6278cf26f2a4b2067ffdf
@@@ -163,7 -126,8 +164,8 @@@ CF_KEYWORDS(TIMEFORMAT, ISO, SHORT, LON
  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(CORK, SORTED, TRIE, MIN, MAX, ROA, ROUTE, REFRESH, SETTLE, TIME, GC, THRESHOLD, PERIOD)
+ CF_KEYWORDS(MPLS_LABEL, MPLS_POLICY, MPLS_CLASS)
  
  /* For r_args_channel */
  CF_KEYWORDS(IPV4, IPV4_MC, IPV4_MPLS, IPV6, IPV6_MC, IPV6_MPLS, IPV6_SADR, VPN4, VPN4_MC, VPN4_MPLS, VPN6, VPN6_MC, VPN6_MPLS, ROA4, ROA6, FLOW4, FLOW6, MPLS, PRI, SEC)
  CF_ENUM(T_ENUM_RTS, RTS_, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIRECT,
        RIP, OSPF, OSPF_IA, OSPF_EXT1, OSPF_EXT2, BGP, PIPE, BABEL)
  CF_ENUM(T_ENUM_SCOPE, SCOPE_, HOST, LINK, SITE, ORGANIZATION, UNIVERSE, UNDEFINED)
 -CF_ENUM(T_ENUM_RTD, RTD_, UNICAST, BLACKHOLE, UNREACHABLE, PROHIBIT)
 +CF_ENUM(T_ENUM_RTD, RTD_, BLACKHOLE, UNREACHABLE, PROHIBIT)
  CF_ENUM(T_ENUM_ROA, ROA_, UNKNOWN, VALID, INVALID)
  CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6)
+ CF_ENUM(T_ENUM_MPLS_POLICY, MPLS_POLICY_, NONE, STATIC, PREFIX, AGGREGATE)
  
  %type <i32> idval
  %type <f> imexport
  %type <s> optproto
  %type <ra> r_args
  %type <sd> sym_args
- %type <i> proto_start 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 <i> proto_start debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_mode limit_action net_type net_type_base tos password_algorithm
  %type <ps> proto_patt proto_patt2
  %type <cc> channel_start proto_channel
  %type <cl> limit_spec
diff --cc nest/mpls.Y
index 0000000000000000000000000000000000000000,b4ae990b46c4ba1f8a80f37547abd717793dabbb..c3a03671fdab272154b2a2b672a5ce2da7b37110
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,141 +1,141 @@@
 - | 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; }
+ /*
+  *    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 CF_SYM_KNOWN { cf_assert_symbol($2, SYM_MPLS_DOMAIN); MPLS_CC->domain = $2->mpls_domain; }
++ | LABEL RANGE CF_SYM_KNOWN { cf_assert_symbol($3, SYM_MPLS_RANGE); MPLS_CC->range = $3->mpls_range; }
+  | LABEL RANGE STATIC  { MPLS_CC->range = MPLS_CC->domain->static_range; }
+  | LABEL RANGE DYNAMIC { MPLS_CC->range = MPLS_CC->domain->dynamic_range; }
+  | LABEL POLICY mpls_label_policy { MPLS_CC->label_policy = $3; }
+  ;
+ mpls_channel_opts:
+    /* empty */
+  | mpls_channel_opts mpls_channel_opt ';'
+  ;
+ mpls_channel_opt_list:
+    /* empty */
+  | '{' mpls_channel_opts '}'
+  ;
+ mpls_channel_end: { mpls_channel_postconfig(this_channel); } channel_end;
+ CF_CODE
+ CF_END
diff --cc nest/mpls.c
index 0000000000000000000000000000000000000000,1992234ca9c23d2cded4b21e697003279a54c8ac..96bafcb3f6a49358495645439b742ff55ee74b4a
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1001 +1,995 @@@
 -  struct pool *p = rp_new(&root_pool, "MPLS domain");
+ /*
+  *    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)
+ {
 -#define RTA_KEY(fec)          fec->rta, fec->class_id, fec->hash
++  struct pool *p = rp_new(&root_pool, the_bird_domain.the_bird, "MPLS domain");
+   struct mpls_domain *m = mb_allocz(p, sizeof(struct mpls_domain));
+   m->cf = cf;
+   m->name = cf->name;
+   m->pool = p;
+   lmap_init(&m->labels, p);
+   lmap_set(&m->labels, 0);
+   init_list(&m->ranges);
+   init_list(&m->handles);
+   struct mpls_range_config *rc;
+   WALK_LIST(rc, cf->ranges)
+     mpls_new_range(m, rc);
+   add_tail(&mpls_domains, &m->n);
+   cf->domain = m;
+   return m;
+ }
+ static struct mpls_domain *
+ mpls_find_domain_(list *l, const char *name)
+ {
+   struct mpls_domain *m;
+   WALK_LIST(m, *l)
+     if (!strcmp(m->name, name))
+       return m;
+   return NULL;
+ }
+ static int
+ mpls_reconfigure_domain(struct mpls_domain *m, struct mpls_domain_config *cf)
+ {
+   cf->domain = m;
+   m->cf->domain = NULL;
+   m->cf = cf;
+   m->name = cf->name;
+   /* Reconfigure label ranges */
+   list old_ranges;
+   init_list(&old_ranges);
+   add_tail_list(&old_ranges, &m->ranges);
+   init_list(&m->ranges);
+   struct mpls_range_config *rc;
+   WALK_LIST(rc, cf->ranges)
+   {
+     struct mpls_range *r = mpls_find_range_(&old_ranges, rc->name);
+     if (r && mpls_reconfigure_range(m, r, rc))
+     {
+       rem_node(&r->n);
+       add_tail(&m->ranges, &r->n);
+       continue;
+     }
+     mpls_new_range(m, rc);
+   }
+   struct mpls_range *r, *r2;
+   WALK_LIST_DELSAFE(r, r2, old_ranges)
+     mpls_remove_range(r);
+   add_tail_list(&m->ranges, &old_ranges);
+   return 1;
+ }
+ static void
+ mpls_free_domain(struct mpls_domain *m)
+ {
+   ASSERT(m->use_count == 0);
+   ASSERT(m->label_count == 0);
+   ASSERT(EMPTY_LIST(m->handles));
+   struct config *cfg = m->removed;
+   m->cf->domain = NULL;
+   rem_node(&m->n);
+   rfree(m->pool);
+   config_del_obstacle(cfg);
+ }
+ static void
+ mpls_remove_domain(struct mpls_domain *m, struct config *cfg)
+ {
+   m->removed = cfg;
+   config_add_obstacle(cfg);
+   if (!m->use_count)
+     mpls_free_domain(m);
+ }
+ void
+ mpls_lock_domain(struct mpls_domain *m)
+ {
+   m->use_count++;
+ }
+ void
+ mpls_unlock_domain(struct mpls_domain *m)
+ {
+   ASSERT(m->use_count > 0);
+   m->use_count--;
+   if (!m->use_count && m->removed)
+     mpls_free_domain(m);
+ }
+ void
+ mpls_preconfig(struct config *c)
+ {
+   init_list(&c->mpls_domains);
+ }
+ void
+ mpls_commit(struct config *new, struct config *old)
+ {
+   list old_domains;
+   init_list(&old_domains);
+   add_tail_list(&old_domains, &mpls_domains);
+   init_list(&mpls_domains);
+   struct mpls_domain_config *mc;
+   WALK_LIST(mc, new->mpls_domains)
+   {
+     struct mpls_domain *m = mpls_find_domain_(&old_domains, mc->name);
+     if (m && mpls_reconfigure_domain(m, mc))
+     {
+       rem_node(&m->n);
+       add_tail(&mpls_domains, &m->n);
+       continue;
+     }
+     mpls_new_domain(mc);
+   }
+   struct mpls_domain *m, *m2;
+   WALK_LIST_DELSAFE(m, m2, old_domains)
+     mpls_remove_domain(m, old);
+   add_tail_list(&mpls_domains, &old_domains);
+ }
+ /*
+  *    MPLS range
+  */
+ struct mpls_range_config *
+ mpls_range_config_new(struct mpls_domain_config *mc, struct symbol *s)
+ {
+   struct mpls_range_config *rc = cfg_allocz(sizeof(struct mpls_range_config));
+   if (s)
+     cf_define_symbol(new_config, s, SYM_MPLS_RANGE, mpls_range, rc);
+   rc->domain = mc;
+   rc->name = s ? s->name : NULL;
+   rc->start = (uint) -1;
+   rc->length = (uint) -1;
+   add_tail(&mc->ranges, &rc->n);
+   return rc;
+ }
+ static struct mpls_range *
+ mpls_new_range(struct mpls_domain *m, struct mpls_range_config *cf)
+ {
+   struct mpls_range *r = mb_allocz(m->pool, sizeof(struct mpls_range));
+   r->cf = cf;
+   r->name = cf->name;
+   r->lo = cf->start;
+   r->hi = cf->start + cf->length;
+   add_tail(&m->ranges, &r->n);
+   cf->range = r;
+   return r;
+ }
+ static struct mpls_range *
+ mpls_find_range_(list *l, const char *name)
+ {
+   struct mpls_range *r;
+   WALK_LIST(r, *l)
+     if (!strcmp(r->name, name))
+       return r;
+   return NULL;
+ }
+ static int
+ mpls_reconfigure_range(struct mpls_domain *m UNUSED, struct mpls_range *r, struct mpls_range_config *cf)
+ {
+   if ((cf->start > r->lo) || (cf->start + cf->length < r->hi))
+     return 0;
+   cf->range = r;
+   r->cf->range = NULL;
+   r->cf = cf;
+   r->name = cf->name;
+   r->lo = cf->start;
+   r->hi = cf->start + cf->length;
+   return 1;
+ }
+ static void
+ mpls_free_range(struct mpls_range *r)
+ {
+   ASSERT(r->use_count == 0);
+   ASSERT(r->label_count == 0);
+   r->cf->range = NULL;
+   rem_node(&r->n);
+   mb_free(r);
+ }
+ static void
+ mpls_remove_range(struct mpls_range *r)
+ {
+   r->removed = 1;
+   if (!r->use_count)
+     mpls_free_range(r);
+ }
+ void
+ mpls_lock_range(struct mpls_range *r)
+ {
+   r->use_count++;
+ }
+ void
+ mpls_unlock_range(struct mpls_range *r)
+ {
+   ASSERT(r->use_count > 0);
+   r->use_count--;
+   if (!r->use_count && r->removed)
+     mpls_free_range(r);
+ }
+ /*
+  *    MPLS handle
+  */
+ struct mpls_handle *
+ mpls_new_handle(struct mpls_domain *m, struct mpls_range *r)
+ {
+   struct mpls_handle *h = mb_allocz(m->pool, sizeof(struct mpls_handle));
+   h->range = r;
+   mpls_lock_range(h->range);
+   add_tail(&m->handles, &h->n);
+   return h;
+ }
+ void
+ mpls_free_handle(struct mpls_domain *m UNUSED, struct mpls_handle *h)
+ {
+   ASSERT(h->label_count == 0);
+   mpls_unlock_range(h->range);
+   rem_node(&h->n);
+   mb_free(h);
+ }
+ /*
+  *    MPLS label
+  */
+ uint
+ mpls_new_label(struct mpls_domain *m, struct mpls_handle *h)
+ {
+   struct mpls_range *r = h->range;
+   uint n = lmap_first_zero_in_range(&m->labels, r->lo, r->hi);
+   if (n >= r->hi)
+     return 0;
+   m->label_count++;
+   r->label_count++;
+   h->label_count++;
+   lmap_set(&m->labels, n);
+   return n;
+ }
+ void
+ mpls_free_label(struct mpls_domain *m, struct mpls_handle *h, uint n)
+ {
+   struct mpls_range *r = h->range;
+   ASSERT(lmap_test(&m->labels, n));
+   lmap_clear(&m->labels, n);
+   ASSERT(m->label_count);
+   m->label_count--;
+   ASSERT(r->label_count);
+   r->label_count--;
+   ASSERT(h->label_count);
+   h->label_count--;
+ }
+ /*
+  *    MPLS channel
+  */
+ static void
+ mpls_channel_init(struct channel *C, struct channel_config *CC)
+ {
+   struct mpls_channel *c = (void *) C;
+   struct mpls_channel_config *cc = (void *) CC;
+   c->domain = cc->domain->domain;
+   c->range = cc->range->range;
+   c->label_policy = cc->label_policy;
+ }
+ static int
+ mpls_channel_start(struct channel *C)
+ {
+   struct mpls_channel *c = (void *) C;
+   mpls_lock_domain(c->domain);
+   mpls_lock_range(c->range);
+   return 0;
+ }
+ /*
+ static void
+ mpls_channel_shutdown(struct channel *C)
+ {
+   struct mpls_channel *c = (void *) C;
+ }
+ */
+ static void
+ mpls_channel_cleanup(struct channel *C)
+ {
+   struct mpls_channel *c = (void *) C;
+   mpls_unlock_range(c->range);
+   mpls_unlock_domain(c->domain);
+ }
+ static int
+ mpls_channel_reconfigure(struct channel *C, struct channel_config *CC, int *import_changed UNUSED, int *export_changed UNUSED)
+ {
+   struct mpls_channel *c = (void *) C;
+   struct mpls_channel_config *new = (void *) CC;
+   if ((new->domain->domain != c->domain) ||
+       (new->range->range != c->range) ||
+       (new->label_policy != c->label_policy))
+     return 0;
+   return 1;
+ }
+ void
+ mpls_channel_postconfig(struct channel_config *CC)
+ {
+   struct mpls_channel_config *cc = (void *) CC;
+   if (!cc->domain)
+     cf_error("MPLS domain not specified");
+   if (!cc->range)
+     cc->range = (cc->label_policy == MPLS_POLICY_STATIC) ?
+       cc->domain->static_range : cc->domain->dynamic_range;
+   if (cc->range->domain != cc->domain)
+     cf_error("MPLS label range from different MPLS domain");
+   if (!cc->c.table)
+     cf_error("Routing table not specified");
+ }
+ struct channel_class channel_mpls = {
+   .channel_size =     sizeof(struct mpls_channel),
+   .config_size =      sizeof(struct mpls_channel_config),
+   .init =             mpls_channel_init,
+   .start =            mpls_channel_start,
+ //  .shutdown =               mpls_channel_shutdown,
+   .cleanup =          mpls_channel_cleanup,
+   .reconfigure =      mpls_channel_reconfigure,
+ };
+ /*
+  *    MPLS FEC map
+  */
+ #define NET_KEY(fec)          fec->net, fec->path_id, fec->hash
+ #define NET_NEXT(fec)         fec->next_k
+ #define NET_EQ(n1,i1,h1,n2,i2,h2) h1 == h2 && i1 == i2 && net_equal(n1, n2)
+ #define NET_FN(n,i,h)         h
+ #define NET_REHASH            mpls_net_rehash
+ #define NET_PARAMS            /8, *2, 2, 2, 8, 24
 -#define RTA_EQ(r1,i1,h1,r2,i2,h2) h1 == h2 && r1 == r2 && i1 == i2
 -#define RTA_FN(r,i,h)         h
++#define RTA_KEY(fec)          fec->rta
+ #define RTA_NEXT(fec)         fec->next_k
 -static rta * mpls_get_key_rta(struct mpls_fec_map *m, const rta *src);
++#define RTA_EQ(r1,r2)         r1 == r2
++#define RTA_FN(r)             r->hash_key
+ #define RTA_REHASH            mpls_rta_rehash
+ #define RTA_PARAMS            /8, *2, 2, 2, 8, 24
+ #define LABEL_KEY(fec)                fec->label
+ #define LABEL_NEXT(fec)               fec->next_l
+ #define LABEL_EQ(l1,l2)               l1 == l2
+ #define LABEL_FN(l)           u32_hash(l)
+ #define LABEL_REHASH          mpls_label_rehash
+ #define LABEL_PARAMS          /8, *2, 2, 2, 8, 24
+ HASH_DEFINE_REHASH_FN(NET, struct mpls_fec)
+ HASH_DEFINE_REHASH_FN(RTA, struct mpls_fec)
+ HASH_DEFINE_REHASH_FN(LABEL, struct mpls_fec)
+ static void mpls_withdraw_fec(struct mpls_fec_map *m, struct mpls_fec *fec);
 -  struct pool *p = rp_new(pp, "MPLS FEC map");
++static struct ea_storage * mpls_get_key_attrs(struct mpls_fec_map *m, ea_list *src);
+ struct mpls_fec_map *
+ mpls_fec_map_new(pool *pp, struct channel *C, uint rts)
+ {
 -  m->mpls_scope = SCOPE_UNIVERSE;
++  struct pool *p = rp_new(pp, the_bird_domain.the_bird, "MPLS FEC map");
+   struct mpls_fec_map *m = mb_allocz(p, sizeof(struct mpls_fec_map));
+   struct mpls_channel *c = (void *) C;
+   m->pool = p;
+   m->channel = C;
+   m->domain = c->domain;
+   mpls_lock_domain(m->domain);
+   m->handle = mpls_new_handle(c->domain, c->range);
+   /* net_hash and rta_hash are initialized on-demand */
+   HASH_INIT(m->label_hash, m->pool, 4);
+   m->mpls_rts = rts;
 -  if (m->rta_hash.data)
+   return m;
+ }
+ void
+ mpls_fec_map_free(struct mpls_fec_map *m)
+ {
+   /* Free stored rtas */
 -    HASH_WALK(m->rta_hash, next_k, fec)
++  if (m->attrs_hash.data)
+   {
 -      rta_free(fec->rta);
++    HASH_WALK(m->attrs_hash, next_k, fec)
+     {
 -mpls_get_fec_by_rta(struct mpls_fec_map *m, const rta *src, u32 class_id)
++      ea_free(fec->rta->l);
+       fec->rta = NULL;
+     }
+     HASH_WALK_END;
+   }
+   /* Free allocated labels */
+   HASH_WALK(m->label_hash, next_l, fec)
+   {
+     if (fec->policy != MPLS_POLICY_STATIC)
+       mpls_free_label(m->domain, m->handle, fec->label);
+   }
+   HASH_WALK_END;
+   mpls_free_handle(m->domain, m->handle);
+   mpls_unlock_domain(m->domain);
+   rfree(m->pool);
+ }
+ static slab *
+ mpls_slab(struct mpls_fec_map *m, uint type)
+ {
+   ASSERT(type <= NET_VPN6);
+   int pos = type ? (type - 1) : 0;
+   if (!m->slabs[pos])
+     m->slabs[pos] = sl_new(m->pool, sizeof(struct mpls_fec) + net_addr_length[pos + 1]);
+   return m->slabs[pos];
+ }
+ struct mpls_fec *
+ mpls_find_fec_by_label(struct mpls_fec_map *m, u32 label)
+ {
+   return HASH_FIND(m->label_hash, LABEL, label);
+ }
+ struct mpls_fec *
+ mpls_get_fec_by_label(struct mpls_fec_map *m, u32 label)
+ {
+   struct mpls_fec *fec = HASH_FIND(m->label_hash, LABEL, label);
+   if (fec)
+     return fec;
+   fec = sl_allocz(mpls_slab(m, 0));
+   fec->label = label;
+   fec->policy = MPLS_POLICY_STATIC;
+   DBG("New FEC lab %u\n", fec->label);
+   HASH_INSERT2(m->label_hash, LABEL, m->pool, fec);
+   return fec;
+ }
+ struct mpls_fec *
+ mpls_get_fec_by_net(struct mpls_fec_map *m, const net_addr *net, u32 path_id)
+ {
+   if (!m->net_hash.data)
+     HASH_INIT(m->net_hash, m->pool, 4);
+   u32 hash = net_hash(net) ^ u32_hash(path_id);
+   struct mpls_fec *fec = HASH_FIND(m->net_hash, NET, net, path_id, hash);
+   if (fec)
+     return fec;
+   fec = sl_allocz(mpls_slab(m, net->type));
+   fec->hash = hash;
+   fec->path_id = path_id;
+   net_copy(fec->net, net);
+   fec->label = mpls_new_label(m->domain, m->handle);
+   fec->policy = MPLS_POLICY_PREFIX;
+   DBG("New FEC net %u\n", fec->label);
+   HASH_INSERT2(m->net_hash, NET, m->pool, fec);
+   HASH_INSERT2(m->label_hash, LABEL, m->pool, fec);
+   return fec;
+ }
+ struct mpls_fec *
 -  if (!m->rta_hash.data)
 -    HASH_INIT(m->rta_hash, m->pool, 4);
++mpls_get_fec_by_destination(struct mpls_fec_map *m, ea_list *dest)
+ {
 -  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 (!m->attrs_hash.data)
++    HASH_INIT(m->attrs_hash, m->pool, 4);
 -    rta_free(rta);
++  struct ea_storage *rta = mpls_get_key_attrs(m, dest);
++  u32 hash = rta->hash_key;
++  struct mpls_fec *fec = HASH_FIND(m->attrs_hash, RTA, rta);
+   if (fec)
+   {
 -  fec->class_id = class_id;
++    ea_free(rta->l);
+     return fec;
+   }
+   fec = sl_allocz(mpls_slab(m, 0));
+   fec->hash = hash;
 -  HASH_INSERT2(m->rta_hash, RTA, m->pool, fec);
+   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);
 -    rta_free(fec->rta);
 -    HASH_REMOVE2(m->rta_hash, RTA, m->pool, fec);
++  HASH_INSERT2(m->attrs_hash, RTA, m->pool, fec);
+   HASH_INSERT2(m->label_hash, LABEL, m->pool, fec);
+   return fec;
+ }
+ void
+ mpls_free_fec(struct mpls_fec_map *m, struct mpls_fec *fec)
+ {
+   if (fec->state != MPLS_FEC_DOWN)
+     mpls_withdraw_fec(m, fec);
+   DBG("Free FEC %u\n", fec->label);
+   mpls_free_label(m->domain, m->handle, fec->label);
+   HASH_REMOVE2(m->label_hash, LABEL, m->pool, fec);
+   switch (fec->policy)
+   {
+   case MPLS_POLICY_STATIC:
+     break;
+   case MPLS_POLICY_PREFIX:
+     HASH_REMOVE2(m->net_hash, NET, m->pool, fec);
+     break;
+   case MPLS_POLICY_AGGREGATE:
 -static inline void
 -mpls_damage_fec(struct mpls_fec_map *m UNUSED, struct mpls_fec *fec)
++    ea_free(fec->rta->l);
++    HASH_REMOVE2(m->attrs_hash, RTA, m->pool, fec);
+     break;
+   default:
+     bug("Unknown fec type");
+   }
+   sl_free(fec);
+ }
+ static inline void mpls_lock_fec(struct mpls_fec_map *x UNUSED, struct mpls_fec *fec)
+ { if (fec) fec->uc++; }
+ static inline void mpls_unlock_fec(struct mpls_fec_map *x, struct mpls_fec *fec)
+ { if (fec && !--fec->uc) mpls_free_fec(x, fec); }
 -  if (fec->state == MPLS_FEC_CLEAN)
 -    fec->state = MPLS_FEC_DIRTY;
++struct mpls_fec_tmp_lock {
++  resource r;
++  struct mpls_fec_map *m;
++  struct mpls_fec *fec;
++};
++
++static void
++mpls_fec_tmp_lock_free(resource *r)
+ {
 -static rta *
 -mpls_get_key_rta(struct mpls_fec_map *m, const rta *src)
++  struct mpls_fec_tmp_lock *l = SKIP_BACK(struct mpls_fec_tmp_lock, r, r);
++  mpls_unlock_fec(l->m, l->fec);
+ }
 -  rta *a = allocz(RTA_MAX_SIZE);
++static void
++mpls_fec_tmp_lock_dump(resource *r, unsigned indent UNUSED)
+ {
 -  a->source = m->mpls_rts;
 -  a->scope = m->mpls_scope;
++  struct mpls_fec_tmp_lock *l = SKIP_BACK(struct mpls_fec_tmp_lock, r, r);
++  debug("map=%p fec=%p label=%u", l->m, l->fec, l->fec->label);
++}
 -  if (!src->hostentry)
 -  {
 -    /* Just copy the nexthop */
 -    a->dest = src->dest;
 -    nexthop_link(a, &src->nh);
 -  }
 -  else
 -  {
 -    /* Keep the hostentry */
 -    a->hostentry = src->hostentry;
++static struct resclass mpls_fec_tmp_lock_class = {
++  .name = "Temporary MPLS FEC Lock",
++  .size = sizeof(struct mpls_fec_tmp_lock),
++  .free = mpls_fec_tmp_lock_free,
++  .dump = mpls_fec_tmp_lock_dump,
++};
 -    /* 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));
 -  }
++static void
++mpls_lock_fec_tmp(struct mpls_fec_map *m, struct mpls_fec *fec)
++{
++  if (!fec)
++    return;
 -  return rta_lookup(a);
++  fec->uc++;
 -static void
 -mpls_announce_fec(struct mpls_fec_map *m, struct mpls_fec *fec, const rta *src)
++  struct mpls_fec_tmp_lock *l = ralloc(tmp_res.pool, &mpls_fec_tmp_lock_class);
++  l->m = m;
++  l->fec = fec;
+ }
 -  rta *a = allocz(RTA_MAX_SIZE);
++static inline void
++mpls_damage_fec(struct mpls_fec_map *m UNUSED, struct mpls_fec *fec)
+ {
 -  a->source = m->mpls_rts;
 -  a->scope = m->mpls_scope;
++  if (fec->state == MPLS_FEC_CLEAN)
++    fec->state = MPLS_FEC_DIRTY;
++}
 -  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;
++static struct ea_storage *
++mpls_get_key_attrs(struct mpls_fec_map *m, ea_list *src)
++{
++  EA_LOCAL_LIST(4) ea = {
++    .l.flags = EALF_SORTED,
++  };
 -    /* Reconstruct the original labelstack */
 -    ms.len = src->nh.labels_orig;
 -    memcpy(ms.stack, labels, src->nh.labels_orig * sizeof(u32));
++  uint last_id = 0;
++  #define PUT_ATTR(cls)       do { \
++    ASSERT_DIE(last_id < (cls)->id); \
++    last_id = (cls)->id; \
++    eattr *a = ea_find_by_class(src, (cls)); \
++    if (a) ea.a[ea.l.count++] = *a; \
++  } while (0)
 -    struct hostentry *s = src->hostentry;
 -    rta_set_recursive_next_hop(m->channel->table, a, s->owner, s->addr, s->link, &ms);
++  PUT_ATTR(&ea_gen_nexthop);
++  PUT_ATTR(&ea_gen_hostentry);
++  ea.a[ea.l.count++] = EA_LITERAL_EMBEDDED(&ea_gen_source, 0, m->mpls_rts);
++  PUT_ATTR(&ea_gen_mpls_class);
++  return ea_get_storage(ea_lookup(&ea.l, 0));
++}
++
++static void
++mpls_announce_fec(struct mpls_fec_map *m, struct mpls_fec *fec, ea_list *src)
++{
++  /* Check existence of hostentry */
++  const struct eattr *heea = ea_find_by_class(src, &ea_gen_hostentry);
++  if (heea) {
+     /* The same hostentry, but different dependent table */
 -  rte *e = rte_get_temp(rta_lookup(a), m->channel->proto->main_source);
 -  e->pflags = 0;
++    struct hostentry_adata *head = SKIP_BACK(struct hostentry_adata, ad, heea->u.ad);
++    struct hostentry *he = head->he;
++    ea_set_hostentry(&src, m->channel->table, he->owner, he->addr, he->link,
++      HOSTENTRY_LABEL_COUNT(head), head->labels);
+   }
+   net_addr_mpls n = NET_ADDR_MPLS(fec->label);
 -  rte_update2(m->channel, (net_addr *) &n, e, m->channel->proto->main_source);
++  rte e = {
++    .src = m->channel->proto->main_source,
++    .attrs = src,
++  };
+   fec->state = MPLS_FEC_CLEAN;
 -  rte_update2(m->channel, (net_addr *) &n, NULL, m->channel->proto->main_source);
++  rte_update(m->channel, (net_addr *) &n, &e, m->channel->proto->main_source);
+ }
+ static void
+ mpls_withdraw_fec(struct mpls_fec_map *m, struct mpls_fec *fec)
+ {
+   net_addr_mpls n = NET_ADDR_MPLS(fec->label);
+   fec->state = MPLS_FEC_DOWN;
 -mpls_apply_fec(rte *r, struct mpls_fec *fec, linpool *lp)
++  rte_update(m->channel, (net_addr *) &n, NULL, m->channel->proto->main_source);
+ }
+ static void
 -  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);
 -  }
++mpls_apply_fec(rte *r, struct mpls_fec *fec)
+ {
 -mpls_handle_rte(struct mpls_fec_map *m, const net_addr *n, rte *r, linpool *lp, struct mpls_fec **locked_fec)
++  ea_set_attr_u32(&r->attrs, &ea_gen_mpls_label, 0, fec->label);
++  ea_set_attr_u32(&r->attrs, &ea_gen_mpls_policy, 0, fec->policy);
+ }
+ void
 -  ASSERT(!(r->flags & REF_COW));
 -
++mpls_handle_rte(struct mpls_fec_map *m, const net_addr *n, rte *r)
+ {
 -
+   struct mpls_fec *fec = NULL;
 -  uint policy = ea_get_int(r->attrs->eattrs, EA_MPLS_POLICY, 0);
+   /* Select FEC for route */
 -    uint label = ea_get_int(r->attrs->eattrs, EA_MPLS_LABEL, 0);
++  uint policy = ea_get_int(r->attrs, &ea_gen_mpls_policy, 0);
+   switch (policy)
+   {
+   case MPLS_POLICY_NONE:
+     return;
+   case MPLS_POLICY_STATIC:;
 -  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);
++    uint label = ea_get_int(r->attrs, &ea_gen_mpls_label, 0);
+     if (label < 16)
+       return;
+     fec = mpls_get_fec_by_label(m, label);
+     mpls_damage_fec(m, fec);
+     break;
+   case MPLS_POLICY_PREFIX:
+     fec = mpls_get_fec_by_net(m, n, r->src->private_id);
+     mpls_damage_fec(m, fec);
+     break;
 -  mpls_lock_fec(m, fec);
 -  *locked_fec = fec;
++  case MPLS_POLICY_AGGREGATE:
++    fec = mpls_get_fec_by_destination(m, r->attrs);
+     break;
+   default:
+     log(L_WARN "Route %N has invalid MPLS policy %u", n, policy);
+     return;
+   }
+   /* Temporarily lock FEC */
 -  mpls_apply_fec(r, fec, lp);
++  mpls_lock_fec_tmp(m, fec);
+   /* Apply FEC label to route */
 -void
 -mpls_handle_rte_cleanup(struct mpls_fec_map *m, struct mpls_fec **locked_fec)
++  mpls_apply_fec(r, fec);
+   /* Announce MPLS rule for new/updated FEC */
+   if (fec->state != MPLS_FEC_CLEAN)
+     mpls_announce_fec(m, fec, r->attrs);
+ }
 -  /* Unlock temporarily locked FEC from mpls_handle_rte() */
 -  if (*locked_fec)
 -  {
 -    mpls_unlock_fec(m, *locked_fec);
 -    *locked_fec = NULL;
 -  }
 -}
++static inline struct mpls_fec_tmp_lock
++mpls_rte_get_fec_lock(const rte *r)
+ {
 -void
 -mpls_rte_insert(net *n UNUSED, rte *r)
 -{
 -  struct proto *p = r->src->proto;
 -  struct mpls_fec_map *m = p->mpls_map;
++  struct mpls_fec_tmp_lock mt = {
++    .m = SKIP_BACK(struct proto, sources, r->src->owner)->mpls_map,
++  };
 -  uint label = ea_get_int(r->attrs->eattrs, EA_MPLS_LABEL, 0);
++  if (!mt.m)
++    return mt;
 -    return;
++  uint label = ea_get_int(r->attrs, &ea_gen_mpls_label, 0);
+   if (label < 16)
 -  struct mpls_fec *fec = mpls_find_fec_by_label(m, label);
 -  if (!fec)
 -    return;
 -
 -  mpls_lock_fec(m, fec);
++    return mt;
 -mpls_rte_remove(net *n UNUSED, rte *r)
++  mt.fec = mpls_find_fec_by_label(mt.m, label);
++  return mt;
+ }
+ void
 -  struct proto *p = r->src->proto;
 -  struct mpls_fec_map *m = p->mpls_map;
++mpls_rte_preimport(rte *new, const rte *old)
+ {
 -  uint label = ea_get_int(r->attrs->eattrs, EA_MPLS_LABEL, 0);
 -  if (label < 16)
 -    return;
++  struct mpls_fec_tmp_lock new_mt = {}, old_mt = {};
 -  struct mpls_fec *fec = mpls_find_fec_by_label(m, label);
 -  if (!fec)
++  if (new)
++    new_mt = mpls_rte_get_fec_lock(new);
 -  mpls_unlock_fec(m, fec);
++  if (old)
++    old_mt = mpls_rte_get_fec_lock(old);
++
++  if (new_mt.fec == old_mt.fec)
+     return;
++  if (new_mt.fec)
++    mpls_lock_fec(new_mt.m, new_mt.fec);
++
++  if (old_mt.fec)
++    mpls_unlock_fec(old_mt.m, old_mt.fec);
+ }
++
++struct ea_class ea_gen_mpls_policy = {
++  .name = "mpls_policy",
++  .type = T_ENUM_MPLS_POLICY,
++};
++
++struct ea_class ea_gen_mpls_class = {
++  .name = "mpls_class",
++  .type = T_INT,
++};
++
++struct ea_class ea_gen_mpls_label = {
++  .name = "mpls_label",
++  .type = T_INT,
++};
diff --cc nest/mpls.h
index 0000000000000000000000000000000000000000,a84ede14adf5a38243582bf2cef8d4a4b051ab85..2d188d02f838eaf2083b7220bd0fd38ac32394c3
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,170 +1,166 @@@
 -    u32 class_id;                     /* Aaggregation class */
+ /*
+  *    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 */
 -    struct rta *rta;
+   };
+   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 */
 -  HASH(struct mpls_fec) rta_hash;     /* Hash table for MPLS_POLICY_AGGREGATE FECs */
++    struct ea_storage *rta;
+     net_addr net[0];
+   };
+ };
+ struct mpls_fec_map {
+   pool *pool;                         /* Pool for FEC map */
+   slab *slabs[4];                     /* Slabs for FEC allocation */
+   HASH(struct mpls_fec) net_hash;     /* Hash table for MPLS_POLICY_PREFIX FECs */
 -  u8 mpls_scope;                      /* Scope  value used for MPLS routes () */
++  HASH(struct mpls_fec) attrs_hash;   /* Hash table for MPLS_POLICY_AGGREGATE FECs */
+   HASH(struct mpls_fec) label_hash;   /* Hash table for FEC lookup by label */
+   struct channel *channel;            /* MPLS channel for FEC announcement */
+   struct mpls_domain *domain;         /* MPLS domain, keeping reference */
+   struct mpls_handle *handle;         /* Handle for allocation of labels */
+   u8 mpls_rts;                                /* Source value used for MPLS routes (RTS_*) */
 -struct mpls_fec *mpls_get_fec_by_rta(struct mpls_fec_map *m, const rta *src, u32 class_id);
+ };
+ 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);
 -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);
++struct mpls_fec *mpls_get_fec_by_destination(struct mpls_fec_map *m, ea_list *dest);
+ void mpls_free_fec(struct mpls_fec_map *x, struct mpls_fec *fec);
++void mpls_handle_rte(struct mpls_fec_map *m, const net_addr *n, rte *r);
++void mpls_rte_preimport(rte *new, const rte *old);
+ #endif
diff --cc nest/proto.c
index bbf41ab1402004aa3b9ae25b171fad6698453a4c,701952fff0f9eddb62bf698c10a131f65b19c5ce..9be4daee5e65c5fb8f07c6f624489c92115cd9f2
@@@ -1283,10 -765,10 +1284,10 @@@ channel_config_new(const struct channel
      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];
 +    tab = rt_get_default_table(new_config, net_type);
    }
  
    if (!cc)
@@@ -1502,21 -956,80 +1503,86 @@@ proto_configure_channel(struct proto *p
    return 1;
  }
  
 -proto_setup_mpls_map(struct proto *p, uint rts, int hooks)
+ /**
+  * 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
 -
 -  if (hooks)
 -  {
 -    p->rte_insert = p->mpls_map ? mpls_rte_insert : NULL;
 -    p->rte_remove = p->mpls_map ? mpls_rte_remove : NULL;
 -  }
++proto_setup_mpls_map(struct proto *p, uint rts)
+ {
+   struct mpls_fec_map *m = p->mpls_map;
+   struct channel *c = p->mpls_channel;
+   if (!m && c)
+   {
+     /*
+      * Note that when called from a protocol start hook, it is called before
+      * mpls_channel_start(). But FEC map locks MPLS domain internally so it does
+      * not depend on lock from MPLS channel.
+      */
+     p->mpls_map = mpls_fec_map_new(p->pool, c, rts);
+   }
+   else if (m && !c)
+   {
+     /*
+      * Note that for reconfiguration, it is called after the MPLS channel has
+      * been already removed. But removal of active MPLS channel would trigger
+      * protocol restart anyways.
+      */
+     mpls_fec_map_free(m);
+     p->mpls_map = NULL;
+   }
+   else if (m && c)
+   {
+     // mpls_fec_map_reconfigure(m, c);
+   }
 -proto_shutdown_mpls_map(struct proto *p, int hooks)
+ }
++
+ /**
+  * proto_shutdown_mpls_map - automatically shutdown FEC map for protocol
+  * @p: affected protocol
+  * @hooks: whether to update rte_insert / rte_remove hooks
+  *
+  * Remove MPLS FEC map of the protocol @p during protocol shutdown.
+  */
+ void
++proto_shutdown_mpls_map(struct proto *p)
+ {
+   struct mpls_fec_map *m = p->mpls_map;
+   if (!m)
+     return;
+   mpls_fec_map_free(m);
+   p->mpls_map = NULL;
++}
++
 +static void
 +proto_cleanup(struct proto *p)
 +{
 +  CALL(p->proto->cleanup, p);
  
 -  if (hooks)
 +  if (p->pool)
    {
 -    p->rte_insert = NULL;
 -    p->rte_remove = NULL;
 +    rp_free(p->pool);
 +    p->pool = NULL;
    }
 +
 +  p->active = 0;
 +  proto_log_state_change(p);
 +
 +  proto_rethink_goal(p);
  }
  
  static void
@@@ -1524,20 -1037,11 +1590,21 @@@ proto_loop_stopped(void *ptr
  {
    struct proto *p = ptr;
  
 -  if (p->do_start)
 -  {
 -    if_feed_baby(p);
 -    p->do_start = 0;
 -  }
 +  ASSERT_DIE(birdloop_inside(&main_birdloop));
 +  ASSERT_DIE(p->loop != &main_birdloop);
 +
 +  p->pool = NULL; /* is freed by birdloop_free() */
 +  birdloop_free(p->loop);
 +  p->loop = &main_birdloop;
 +
 +  proto_cleanup(p);
 +}
 +
++
 +static void
 +proto_event(void *ptr)
 +{
 +  struct proto *p = ptr;
  
    if (p->do_stop)
    {
diff --cc nest/protocol.h
index 4831d23749e3c72f60c3c0e0bb1689257257c2d6,81139c33f8b75514267506f80b76df81ca6c9e6b..e2e3c7498178e7b6a5a10e1e1a502e67ba84006a
@@@ -157,12 -172,11 +158,14 @@@ struct proto 
    list channels;                      /* List of channels to rtables (struct channel) */
    struct channel *main_channel;               /* Primary channel */
    struct rte_src *main_source;                /* Primary route source */
 +  struct rte_owner sources;           /* Route source owner structure */
    struct iface *vrf;                  /* Related VRF instance, NULL if global */
 +  TLIST_LIST(proto_neigh) neighbors;  /* List of neighbor structures */
 +  struct iface_subscription iface_sub;        /* Interface notification subscription */
+   struct channel *mpls_channel;               /* MPLS channel, when used */
+   struct mpls_fec_map *mpls_map;      /* Maps protocol routes to FECs / labels */
  
 -  const char *name;                           /* Name of this instance (== cf->name) */
 +  const char *name;                   /* Name of this instance (== cf->name) */
    u32 debug;                          /* Debugging flags */
    u32 mrtdump;                                /* MRTDump flags */
    uint active_channels;                       /* Number of active channels */
@@@ -677,16 -622,21 +680,20 @@@ 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_table(struct proto *p, rtable *t);
  struct channel *proto_find_channel_by_name(struct proto *p, const char *n);
  struct channel *proto_add_channel(struct proto *p, struct channel_config *cf);
  void proto_remove_channel(struct proto *p, struct channel *c);
  int proto_configure_channel(struct proto *p, struct channel **c, struct channel_config *cf);
 -void proto_setup_mpls_map(struct proto *p, uint rts, int hooks);
 -void proto_shutdown_mpls_map(struct proto *p, int hooks);
++void proto_setup_mpls_map(struct proto *p, uint rts);
++void proto_shutdown_mpls_map(struct proto *p);
  
  void channel_set_state(struct channel *c, uint state);
 -void channel_setup_in_table(struct channel *c);
 -void channel_setup_out_table(struct channel *c);
 -void channel_schedule_reload(struct channel *c);
 +void channel_schedule_reload(struct channel *c, struct channel_import_request *cir);
 +int channel_import_request_prefilter(struct channel_import_request *cir_head, const net_addr *n);
  
  static inline void channel_init(struct channel *c) { channel_set_state(c, CS_START); }
  static inline void channel_open(struct channel *c) { channel_set_state(c, CS_UP); }
diff --cc nest/route.h
index fe100a366e24a902017007871981e639c283bf88,0e502b99e746e0a4702ba8aad8f431decd7174c5..610bc21e3816cdb8fd3c7a6e2986aca6f4c6f6ec
@@@ -158,79 -195,35 +158,80 @@@ struct rtable_private 
    struct f_trie *trie_old;            /* Old prefix trie waiting to be freed */
    u32 trie_lock_count;                        /* Prefix trie locked by walks */
    u32 trie_old_lock_count;            /* Old prefix trie locked by walks */
 +  struct tbf rl_pipe;                 /* Rate limiting token buffer for pipe collisions */
  
 -  list subscribers;                   /* Subscribers for notifications */
 -  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 */
 +};
 +
 +/* The final union private-public rtable structure */
 +typedef union rtable {
 +  struct {
 +    RTABLE_PUBLIC;
 +  };
 +  struct rtable_private priv;
  } rtable;
  
 -struct rt_subscription {
 -  node n;
 -  rtable *tab;
 -  void (*hook)(struct rt_subscription *b);
 -  void *data;
 -};
 +#define RT_IS_LOCKED(tab)     DOMAIN_IS_LOCKED(rtable, (tab)->lock)
  
 -struct rt_flowspec_link {
 -  node n;
 -  rtable *src;
 -  rtable *dst;
 -  u32 uc;
 -};
 +#define RT_LOCK(tab)  ({ LOCK_DOMAIN(rtable, (tab)->lock); &(tab)->priv; })
 +#define RT_UNLOCK(tab)        UNLOCK_DOMAIN(rtable, (tab)->lock)
 +#define RT_PRIV(tab)  ({ ASSERT_DIE(RT_IS_LOCKED((tab))); &(tab)->priv; })
 +#define RT_PUB(tab)   SKIP_BACK(rtable, priv, tab)
 +
 +#define RT_LOCKED(tpub, tpriv) for (struct rtable_private *tpriv = RT_LOCK(tpub); tpriv; RT_UNLOCK(tpriv), (tpriv = NULL))
 +#define RT_LOCKED_IF_NEEDED(_tpub, _tpriv) for ( \
 +    struct rtable_private *_al = RT_IS_LOCKED(_tpub) ? &(_tpub)->priv : NULL, *_tpriv = _al ?: RT_LOCK(_tpub); \
 +    _tpriv; \
 +    _al ?: RT_UNLOCK(_tpriv), (_tpriv = NULL))
 +
 +#define RT_RETURN(tpriv, ...) do { RT_UNLOCK(tpriv); return __VA_ARGS__; } while (0)
 +
 +#define RT_PRIV_SAME(tpriv, tpub)     (&(tpub)->priv == (tpriv))
 +
 +/* Flags for birdloop_flag() */
 +#define RTF_CLEANUP   1
 +#define RTF_NHU               2
 +#define RTF_EXPORT    4
 +#define RTF_DELETE    8
 +
 +extern struct rt_cork {
 +  _Atomic uint active;
 +  event_list queue;
 +  event run;
 +} rt_cork;
 +
 +static inline void rt_cork_acquire(void)
 +{
 +  atomic_fetch_add_explicit(&rt_cork.active, 1, memory_order_acq_rel);
 +}
 +
 +static inline void rt_cork_release(void)
 +{
 +  if (atomic_fetch_sub_explicit(&rt_cork.active, 1, memory_order_acq_rel) == 1)
 +  {
 +    synchronize_rcu();
 +    ev_send(&global_work_list, &rt_cork.run);
 +  }
 +}
 +
 +static inline int rt_cork_check(event *e)
 +{
 +  rcu_read_lock();
 +
 +  int corked = (atomic_load_explicit(&rt_cork.active, memory_order_acquire) > 0);
 +  if (corked)
 +    ev_send(&rt_cork.queue, e);
 +
 +  rcu_read_unlock();
 +
 +  return corked;
 +}
  
 -#define NHU_CLEAN     0
 -#define NHU_SCHEDULED 1
 -#define NHU_RUNNING   2
 -#define NHU_DIRTY     3
  
  typedef struct network {
 -  struct rte *routes;                 /* Available routes for this network */
 +  struct rte_storage *routes;         /* Available routes for this network */
 +  struct rt_pending_export *first, *last;
    struct fib_node n;                  /* FIB flags reserved for kernel syncer */
  } net;
  
@@@ -709,19 -417,339 +710,21 @@@ struct rt_show_data_rtable * rt_show_ad
  #define RSEM_NOEXPORT 3               /* Routes rejected by export filter */
  #define RSEM_EXPORTED 4               /* Routes marked in export map */
  
 -/*
 - *    Route Attributes
 - *
 - *    Beware: All standard BGP attributes must be represented here instead
 - *    of making them local to the route. This is needed to ensure proper
 - *    construction of BGP route attribute lists.
 - */
 -
 -/* Nexthop structure */
 -struct nexthop {
 -  ip_addr gw;                         /* Next hop */
 -  struct iface *iface;                        /* Outgoing interface */
 -  struct nexthop *next;
 -  byte flags;
 -  byte weight;
 -  byte labels_orig;                   /* Number of labels before hostentry was applied */
 -  byte labels;                                /* Number of all labels */
 -  u32 label[0];
 -};
 -
 -#define RNF_ONLINK            0x1     /* Gateway is onlink regardless of IP ranges */
 -
 -
 -struct rte_src {
 -  struct rte_src *next;                       /* Hash chain */
 -  struct proto *proto;                        /* Protocol the source is based on */
 -  u64 private_id;                     /* Private ID, assigned by the protocol */
 -  u32 global_id;                      /* Globally unique ID of the source */
 -  unsigned uc;                                /* Use count */
 -};
 -
 -
 -typedef struct rta {
 -  struct rta *next, **pprev;          /* Hash chain */
 -  u32 uc;                             /* Use count */
 -  u32 hash_key;                               /* Hash over important fields */
 -  struct ea_list *eattrs;             /* Extended Attribute chain */
 -  struct hostentry *hostentry;                /* Hostentry for recursive next-hops */
 -  ip_addr from;                               /* Advertising router */
 -  u32 igp_metric;                     /* IGP metric to next hop (for iBGP routes) */
 -  u16 cached:1;                               /* Are attributes cached? */
 -  u16 source:7;                               /* Route source (RTS_...) */
 -  u16 scope:4;                                /* Route scope (SCOPE_... -- see ip.h) */
 -  u16 dest:4;                         /* Route destination type (RTD_...) */
 -  word pref;
 -  struct nexthop nh;                  /* Next hop */
 -} rta;
 -
 -#define RTS_STATIC 1                  /* Normal static route */
 -#define RTS_INHERIT 2                 /* Route inherited from kernel */
 -#define RTS_DEVICE 3                  /* Device route */
 -#define RTS_STATIC_DEVICE 4           /* Static device route */
 -#define RTS_REDIRECT 5                        /* Learned via redirect */
 -#define RTS_RIP 6                     /* RIP route */
 -#define RTS_OSPF 7                    /* OSPF route */
 -#define RTS_OSPF_IA 8                 /* OSPF inter-area route */
 -#define RTS_OSPF_EXT1 9                       /* OSPF external route type 1 */
 -#define RTS_OSPF_EXT2 10              /* OSPF external route type 2 */
 -#define RTS_BGP 11                    /* BGP route */
 -#define RTS_PIPE 12                   /* Inter-table wormhole */
 -#define RTS_BABEL 13                  /* Babel route */
 -#define RTS_RPKI 14                   /* Route Origin Authorization */
 -#define RTS_PERF 15                   /* Perf checker */
 -#define RTS_AGGREGATED 16             /* Aggregated route */
 -#define RTS_MAX 17
 -
 -#define RTD_NONE 0                    /* Undefined next hop */
 -#define RTD_UNICAST 1                 /* Next hop is neighbor router */
 -#define RTD_BLACKHOLE 2                       /* Silently drop packets */
 -#define RTD_UNREACHABLE 3             /* Reject as unreachable */
 -#define RTD_PROHIBIT 4                        /* Administratively prohibited */
 -#define RTD_MAX 5
 -
 -#define IGP_METRIC_UNKNOWN 0x80000000 /* Default igp_metric used when no other
 -                                         protocol-specific metric is availabe */
 -
 -
 -extern const char * rta_dest_names[RTD_MAX];
 -
 -static inline const char *rta_dest_name(uint n)
 -{ return (n < RTD_MAX) ? rta_dest_names[n] : "???"; }
 -
 -/* Route has regular, reachable nexthop (i.e. not RTD_UNREACHABLE and like) */
 -static inline int rte_is_reachable(rte *r)
 -{ return r->attrs->dest == RTD_UNICAST; }
 -
 -
 -/*
 - *    Extended Route Attributes
 - */
 -
 -typedef struct eattr {
 -  word id;                            /* EA_CODE(PROTOCOL_..., protocol-dependent ID) */
 -  byte flags;                         /* Protocol-dependent flags */
 -  byte type:5;                                /* Attribute type */
 -  byte originated:1;                  /* The attribute has originated locally */
 -  byte fresh:1;                               /* An uncached attribute (e.g. modified in export filter) */
 -  byte undef:1;                               /* Explicitly undefined */
 -  union {
 -    uintptr_t data;
 -    const struct adata *ptr;          /* Attribute data elsewhere */
 -  } u;
 -} eattr;
 -
 -
 -#define EA_CODE(proto,id) (((proto) << 8) | (id))
 -#define EA_ID(ea) ((ea) & 0xff)
 -#define EA_PROTO(ea) ((ea) >> 8)
 -#define EA_CUSTOM(id) ((id) | EA_CUSTOM_BIT)
 -#define EA_IS_CUSTOM(ea) ((ea) & EA_CUSTOM_BIT)
 -#define EA_CUSTOM_ID(ea) ((ea) & ~EA_CUSTOM_BIT)
 -
 -const char *ea_custom_name(uint ea);
 -
 -#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
 -#define EA_ALLOW_UNDEF 0x10000                /* ea_find: allow EAF_TYPE_UNDEF */
 -#define EA_BIT(n) ((n) << 24)         /* Used in bitfield accessors */
 -#define EA_BIT_GET(ea) ((ea) >> 24)
 -
 -#define EAF_TYPE_MASK 0x1f            /* Mask with this to get type */
 -#define EAF_TYPE_INT 0x01             /* 32-bit unsigned integer number */
 -#define EAF_TYPE_OPAQUE 0x02          /* Opaque byte string (not filterable) */
 -#define EAF_TYPE_IP_ADDRESS 0x04      /* IP address */
 -#define EAF_TYPE_ROUTER_ID 0x05               /* Router ID (IPv4 address) */
 -#define EAF_TYPE_AS_PATH 0x06         /* BGP AS path (encoding per RFC 1771:4.3) */
 -#define EAF_TYPE_BITFIELD 0x09                /* 32-bit embedded bitfield */
 -#define EAF_TYPE_INT_SET 0x0a         /* Set of u32's (e.g., a community list) */
 -#define EAF_TYPE_EC_SET 0x0e          /* Set of pairs of u32's - ext. community list */
 -#define EAF_TYPE_LC_SET 0x12          /* Set of triplets of u32's - large community list */
 -#define EAF_TYPE_IFACE 0x16           /* Interface pointer stored in adata */
 -#define EAF_EMBEDDED 0x01             /* Data stored in eattr.u.data (part of type spec) */
 -#define EAF_VAR_LENGTH 0x02           /* Attribute length is variable (part of type spec) */
 -
 -typedef struct adata {
 -  uint length;                                /* Length of data */
 -  byte data[0];
 -} adata;
 -
 -extern const adata null_adata;                /* adata of length 0 */
 -
 -static inline struct adata *
 -lp_alloc_adata(struct linpool *pool, uint len)
 -{
 -  struct adata *ad = lp_alloc(pool, sizeof(struct adata) + len);
 -  ad->length = len;
 -  return ad;
 -}
 -
 -static inline int adata_same(const struct adata *a, const struct adata *b)
 -{ return (a->length == b->length && !memcmp(a->data, b->data, a->length)); }
 -
 -
 -typedef struct ea_list {
 -  struct ea_list *next;                       /* In case we have an override list */
 -  byte flags;                         /* Flags: EALF_... */
 -  byte rfu;
 -  word count;                         /* Number of attributes */
 -  eattr attrs[0];                     /* Attribute definitions themselves */
 -} ea_list;
 -
 -#define EALF_SORTED 1                 /* Attributes are sorted by code */
 -#define EALF_BISECT 2                 /* Use interval bisection for searching */
 -#define EALF_CACHED 4                 /* Attributes belonging to cached rta */
 -
 -struct rte_src *rt_find_source(struct proto *p, u32 id);
 -struct rte_src *rt_get_source(struct proto *p, u32 id);
 -static inline void rt_lock_source(struct rte_src *src) { src->uc++; }
 -static inline void rt_unlock_source(struct rte_src *src) { src->uc--; }
 -void rt_prune_sources(void);
 -
 -struct ea_walk_state {
 -  ea_list *eattrs;                    /* Ccurrent ea_list, initially set by caller */
 -  eattr *ea;                          /* Current eattr, initially NULL */
 -  u32 visited[4];                     /* Bitfield, limiting max to 128 */
 +/* Host entry: Resolve hook for recursive nexthops */
 +extern struct ea_class ea_gen_hostentry;
 +struct hostentry_adata {
 +  adata ad;
 +  struct hostentry *he;
 +  u32 labels[0];
  };
  
 -eattr *ea_find(ea_list *, unsigned ea);
 -eattr *ea_walk(struct ea_walk_state *s, uint id, uint max);
 -uintptr_t ea_get_int(ea_list *, unsigned ea, uintptr_t def);
 -void ea_dump(ea_list *);
 -void ea_sort(ea_list *);              /* Sort entries in all sub-lists */
 -unsigned ea_scan(ea_list *);          /* How many bytes do we need for merged ea_list */
 -void ea_merge(ea_list *from, ea_list *to); /* Merge sub-lists to allocated buffer */
 -int ea_same(ea_list *x, ea_list *y);  /* Test whether two ea_lists are identical */
 -uint ea_hash(ea_list *e);     /* Calculate 16-bit hash value */
 -ea_list *ea_append(ea_list *to, ea_list *what);
 -void ea_format_bitfield(const struct eattr *a, byte *buf, int bufsize, const char **names, int min, int max);
 -
 -#define ea_normalize(ea) do { \
 -  if (ea->next) { \
 -    ea_list *t = alloca(ea_scan(ea)); \
 -    ea_merge(ea, t); \
 -    ea = t; \
 -  } \
 -  ea_sort(ea); \
 -  if (ea->count == 0) \
 -    ea = NULL; \
 -} while(0) \
 -
 -struct ea_one_attr_list {
 -  ea_list l;
 -  eattr a;
 -};
++#define HOSTENTRY_LABEL_COUNT(head)   (head->ad.length + sizeof(struct adata) - sizeof(struct hostentry_adata)) / sizeof(u32)
 -static inline eattr *
 -ea_set_attr(ea_list **to, struct linpool *pool, uint id, uint flags, uint type, uintptr_t val)
 -{
 -  struct ea_one_attr_list *ea = lp_alloc(pool, sizeof(*ea));
 -  *ea = (struct ea_one_attr_list) {
 -    .l.flags = EALF_SORTED,
 -    .l.count = 1,
 -    .l.next = *to,
 -
 -    .a.id = id,
 -    .a.type = type,
 -    .a.flags = flags,
 -  };
 -
 -  if (type & EAF_EMBEDDED)
 -    ea->a.u.data = val;
 -  else
 -    ea->a.u.ptr = (struct adata *) val;
 -
 -  *to = &ea->l;
 -
 -  return &ea->a;
 -}
 -
 -static inline void
 -ea_unset_attr(ea_list **to, struct linpool *pool, _Bool local, uint code)
 -{
 -  struct ea_one_attr_list *ea = lp_alloc(pool, sizeof(*ea));
 -  *ea = (struct ea_one_attr_list) {
 -    .l.flags = EALF_SORTED,
 -    .l.count = 1,
 -    .l.next = *to,
 -    .a.id = code,
 -    .a.fresh = local,
 -    .a.originated = local,
 -    .a.undef = 1,
 -  };
 -
 -  *to = &ea->l;
 -}
 -
 -static inline void
 -ea_set_attr_u32(ea_list **to, struct linpool *pool, uint id, uint flags, uint type, u32 val)
 -{ ea_set_attr(to, pool, id, flags, type, (uintptr_t) val); }
 -
 -static inline void
 -ea_set_attr_ptr(ea_list **to, struct linpool *pool, uint id, uint flags, uint type, struct adata *val)
 -{ ea_set_attr(to, pool, id, flags, type, (uintptr_t) val); }
 -
 -static inline void
 -ea_set_attr_data(ea_list **to, struct linpool *pool, uint id, uint flags, uint type, void *data, uint len)
 -{
 -  struct adata *a = lp_alloc_adata(pool, len);
 -  memcpy(a->data, data, len);
 -  ea_set_attr(to, pool, id, flags, type, (uintptr_t) a);
 -}
 -
 -
 -#define NEXTHOP_MAX_SIZE (sizeof(struct nexthop) + sizeof(u32)*MPLS_MAX_LABEL_STACK)
 -
 -static inline size_t nexthop_size(const struct nexthop *nh)
 -{ return sizeof(struct nexthop) + sizeof(u32)*nh->labels; }
 -int nexthop__same(struct nexthop *x, struct nexthop *y); /* Compare multipath nexthops */
 -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, 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);
 -
 -void rta_init(void);
 -static inline size_t rta_size(const rta *a) { return sizeof(rta) + sizeof(u32)*a->nh.labels; }
 -#define RTA_MAX_SIZE (sizeof(rta) + sizeof(u32)*MPLS_MAX_LABEL_STACK)
 -rta *rta_lookup(rta *);                       /* Get rta equivalent to this one, uc++ */
 -static inline int rta_is_cached(rta *r) { return r->cached; }
 -static inline rta *rta_clone(rta *r) { r->uc++; return r; }
 -void rta__free(rta *r);
 -static inline void rta_free(rta *r) { if (r && !--r->uc) rta__free(r); }
 -rta *rta_do_cow(rta *o, linpool *lp);
 -static inline rta * rta_cow(rta *r, linpool *lp) { return rta_is_cached(r) ? rta_do_cow(r, lp) : r; }
 -void rta_dump(rta *);
 -void rta_dump_all(void);
 -void rta_show(struct cli *, rta *);
 -
 -u32 rt_get_igp_metric(rte *rt);
 -struct hostentry * rt_get_hostentry(rtable *tab, ip_addr a, ip_addr ll, rtable *dep);
 -void rta_apply_hostentry(rta *a, struct hostentry *he, mpls_label_stack *mls);
 -
 -static inline void
 -rta_set_recursive_next_hop(rtable *dep, rta *a, rtable *tab, ip_addr gw, ip_addr ll, mpls_label_stack *mls)
 -{
 -  rta_apply_hostentry(a, rt_get_hostentry(tab, gw, ll, dep), mls);
 -}
 -
 -/*
 - * rta_set_recursive_next_hop() acquires hostentry from hostcache and fills
 - * rta->hostentry field.  New hostentry has zero use count. Cached rta locks its
 - * hostentry (increases its use count), uncached rta does not lock it. Hostentry
 - * with zero use count is removed asynchronously during host cache update,
 - * therefore it is safe to hold such hostentry temorarily. Hostentry holds a
 - * lock for a 'source' rta, mainly to share multipath nexthops.
 - *
 - * There is no need to hold a lock for hostentry->dep table, because that table
 - * contains routes responsible for that hostentry, and therefore is non-empty if
 - * given hostentry has non-zero use count. If the hostentry has zero use count,
 - * the entry is removed before dep is referenced.
 - *
 - * The protocol responsible for routes with recursive next hops should hold a
 - * lock for a 'source' table governing that routes (argument tab to
 - * rta_set_recursive_next_hop()), because its routes reference hostentries
 - * (through rta) related to the governing table. When all such routes are
 - * removed, rtas are immediately removed achieving zero uc. Then the 'source'
 - * table lock could be immediately released, although hostentries may still
 - * exist - they will be freed together with the 'source' table.
 - */
 -
 -static inline void rt_lock_hostentry(struct hostentry *he) { if (he) he->uc++; }
 -static inline void rt_unlock_hostentry(struct hostentry *he) { if (he) he->uc--; }
 -
 -int rt_flowspec_check(rtable *tab_ip, rtable *tab_flow, const net_addr *n, rta *a, int interior);
 +void
 +ea_set_hostentry(ea_list **to, rtable *dep, rtable *tab, ip_addr gw, ip_addr ll, u32 lnum, u32 labels[lnum]);
  
 +void ea_show_hostentry(const struct adata *ad, byte *buf, uint size);
 +void ea_show_nexthop_list(struct cli *c, struct nexthop_adata *nhad);
  
  /*
   *    Default protocol preferences
diff --cc nest/rt-attr.c
index 53111696ddbf6f660ff04d9ece054e54245f1620,5b86a41880b440f9aa3a2a90cca8ccf8577954fe..32723ded811cd6e9b42ffadbe80b358c84acea60
@@@ -1701,20 -1370,6 +1701,25 @@@ rta_init(void
  
    rta_alloc_hash();
    rte_src_init();
 +  ea_class_init();
 +
 +  RTA_UNLOCK;
 +
 +  /* These attributes are required to be first for nice "show route" output */
 +  ea_register_init(&ea_gen_nexthop);
 +  ea_register_init(&ea_gen_hostentry);
 +
 +  /* Other generic route attributes */
 +  ea_register_init(&ea_gen_preference);
 +  ea_register_init(&ea_gen_igp_metric);
 +  ea_register_init(&ea_gen_from);
 +  ea_register_init(&ea_gen_source);
 +  ea_register_init(&ea_gen_flowspec_valid);
++
++  /* MPLS route attributes */
++  ea_register_init(&ea_gen_mpls_policy);
++  ea_register_init(&ea_gen_mpls_class);
++  ea_register_init(&ea_gen_mpls_label);
  }
  
  /*
diff --cc nest/rt-show.c
index 5d43317ac72387689f7c48afe0c99b6e65710cd2,265d5c4472d3d53989ab9a0786b01b0ff511a9c0..00d2aa5c278fdff4c4f1ed45f4d58225217e8f41
@@@ -38,19 -42,14 +38,19 @@@ rt_show_rte(struct cli *c, byte *ia, rt
  {
    byte from[IPA_MAX_TEXT_LENGTH+8];
    byte tm[TM_DATETIME_BUFFER_SIZE], info[256];
 -  rta *a = e->attrs;
 -  int sync_error = d->kernel ? krt_get_sync_error(d->kernel, e) : 0;
 -  void (*get_route_info)(struct rte *, byte *buf);
 -  struct nexthop *nh;
 +  ea_list *a = e->attrs;
 +  int sync_error = d->tab->kernel ? krt_get_sync_error(d->tab->kernel, e) : 0;
 +  void (*get_route_info)(const rte *, byte *buf);
-   eattr *nhea = net_type_match(e->net, NB_DEST) ?
++  const eattr *nhea = net_type_match(e->net, NB_DEST) ?
 +    ea_find(a, &ea_gen_nexthop) : NULL;
 +  struct nexthop_adata *nhad = nhea ? (struct nexthop_adata *) nhea->u.ptr : NULL;
 +  int dest = nhad ? (NEXTHOP_IS_REACHABLE(nhad) ? RTD_UNICAST : nhad->dest) : RTD_NONE;
 +  int flowspec_valid = net_is_flow(e->net) ? rt_get_flowspec_valid(e) : FLOWSPEC_UNKNOWN;
  
    tm_format_time(tm, &config->tf_route, e->lastmod);
 -  if (ipa_nonzero(a->from) && !ipa_equal(a->from, a->nh.gw))
 -    bsprintf(from, " from %I", a->from);
 +  ip_addr a_from = ea_get_ip(a, &ea_gen_from, IPA_NONE);
 +  if (ipa_nonzero(a_from) && (!nhad || !ipa_equal(a_from, nhad->nh.gw)))
 +    bsprintf(from, " from %I", a_from);
    else
      from[0] = 0;
  
    if (get_route_info)
      get_route_info(e, info);
    else
 -    bsprintf(info, " (%d)", a->pref);
 +    bsprintf(info, " (%d)", rt_get_preference(e));
  
    if (d->last_table != d->tab)
 -    rt_show_table(c, d);
 -
 -  cli_printf(c, -1007, "%-20s %s [%s %s%s]%s%s", ia, rta_dest_name(a->dest),
 -           e->src->proto->name, tm, from, primary ? (sync_error ? " !" : " *") : "", info);
 -
 -  if (a->dest == RTD_UNICAST)
 -    for (nh = &(a->nh); nh; nh = nh->next)
 -    {
 -      char mpls[MPLS_MAX_LABEL_STACK*12 + 5], *lsp = mpls;
 -      char *onlink = (nh->flags & RNF_ONLINK) ? " onlink" : "";
 -      char weight[16] = "";
 -
 -      if (nh->labels)
 -      {
 -        lsp += bsprintf(lsp, " mpls %d", nh->label[0]);
 -        for (int i=1;i<nh->labels; i++)
 -          lsp += bsprintf(lsp, "/%d", nh->label[i]);
 -      }
 -      *lsp = '\0';
 +    rt_show_table(d);
  
-   eattr *heea;
 -      if (a->nh.next)
 -      bsprintf(weight, " weight %d", nh->weight + 1);
++  const eattr *heea;
 +  struct hostentry_adata *had = NULL;
 +  if (!net_is_flow(e->net) && (dest == RTD_NONE) && (heea = ea_find(a, &ea_gen_hostentry)))
 +    had = (struct hostentry_adata *) heea->u.ptr;
  
 -      if (ipa_nonzero(nh->gw))
 -      cli_printf(c, -1007, "\tvia %I on %s%s%s%s",
 -                 nh->gw, nh->iface->name, mpls, onlink, weight);
 -      else
 -      cli_printf(c, -1007, "\tdev %s%s%s",
 -                 nh->iface->name, mpls,  onlink, weight);
 -    }
 +  cli_printf(c, -1007, "%-20s %s [%s %s%s]%s%s", ia,
 +      net_is_flow(e->net) ? flowspec_valid_name(flowspec_valid) : had ? "recursive" : rta_dest_name(dest),
 +      e->src->owner->name, tm, from, primary ? (sync_error ? " !" : " *") : "", info);
  
    if (d->verbose)
 -    rta_show(c, a);
 +  {
 +    ea_show_list(c, a);
 +    cli_printf(c, -1008, "\tInternal route handling values: %luL %uG %uS id %u",
 +      e->src->private_id, e->src->global_id, e->stale_cycle, e->id);
 +  }
 +  else if (dest == RTD_UNICAST)
 +    ea_show_nexthop_list(c, nhad);
 +  else if (had)
 +  {
 +    char hetext[256];
 +    ea_show_hostentry(&had->ad, hetext, sizeof hetext);
 +    cli_printf(c, -1007, "\t%s", hetext);
 +  }
  }
  
  static void
 -rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
 +rt_show_net(struct rt_show_data *d, const net_addr *n, const rte **feed, uint count)
  {
 -  rte *e, *ee;
 +  struct cli *c = d->cli;
-   byte ia[NET_MAX_TEXT_LENGTH+1];
+   byte ia[NET_MAX_TEXT_LENGTH+16+1];
    struct channel *ec = d->tab->export_channel;
  
    /* The Clang static analyzer complains that ec may be NULL.
  
    int first = 1;
    int first_show = 1;
 -  int last_label = 0;
++  uint last_label = 0;
    int pass = 0;
  
 -  for (e = n->routes; e; e = e->next)
 +  for (uint i = 0; i < count; i++)
      {
 -      if (rte_is_filtered(e) != d->filtered)
 +      if (!d->tab->prefilter && (rte_is_filtered(feed[i]) != d->filtered))
        continue;
  
        d->rt_counter++;
  
        if (d->stats < 2)
        {
-       if (first_show)
-         net_format(n, ia, sizeof(ia));
 -      int label = (int) ea_get_int(e->attrs->eattrs, EA_MPLS_LABEL, (uint) -1);
++      uint label = ea_get_int(e.attrs, &ea_gen_mpls_label, ~0U);
+       if (first_show || (last_label != label))
+       {
 -        if (label < 0)
 -          net_format(n->n.addr, ia, sizeof(ia));
++        if (!~label)
++          net_format(n, ia, sizeof(ia));
+         else
 -          bsnprintf(ia, sizeof(ia), "%N mpls %d", n->n.addr, label);
++          bsnprintf(ia, sizeof(ia), "%N mpls %d", n, label);
+       }
        else
          ia[0] = 0;
  
 -      rt_show_rte(c, ia, e, d, (e->net->routes == ee));
 +      rt_show_rte(c, ia, &e, d, !d->tab->prefilter && !i);
        first_show = 0;
+       last_label = label;
        }
  
        d->show_counter++;
diff --cc nest/rt-table.c
index 94576191bb24f20393fd4dd28cd80b564537cc20,31c59ccb3a48ac3db89188101c3ea947d431dc65..e074c0bf95ae9fe69b167d6f5a7a9cc2e2b48552
@@@ -1850,237 -1453,62 +1851,240 @@@ rte_recalculate(struct rtable_private *
        /* The fourth (empty) case - suboptimal route was removed, nothing to do */
      }
  
 -  if (new)
 +  if (new_stored)
      {
        new->lastmod = current_time();
 +      new->id = hmap_first_zero(&table->id_map);
 +      hmap_set(&table->id_map, new->id);
 +    }
  
 -      if (!old)
 -        {
 -        new->id = hmap_first_zero(&table->id_map);
 -        hmap_set(&table->id_map, new->id);
 -      }
 +  /* Log the route change */
 +  if (new_ok)
 +    rt_rte_trace_in(D_ROUTES, req, &new_stored->rte, new_stored == net->routes ? "added [best]" : "added");
 +  else if (old_ok)
 +    {
 +      if (old != old_best)
 +      rt_rte_trace_in(D_ROUTES, req, old, "removed");
 +      else if (net->routes && rte_is_ok(&net->routes->rte))
 +      rt_rte_trace_in(D_ROUTES, req, old, "removed [replaced]");
        else
 -      new->id = old->id;
 +      rt_rte_trace_in(D_ROUTES, req, old, "removed [sole]");
      }
 +  else
 +    if (req->trace_routes & D_ROUTES)
 +      log(L_TRACE "%s > ignored %N %s->%s", req->name, net->n.addr, old ? "filtered" : "none", new ? "filtered" : "none");
  
 -  /* Log the route change */
 -  if ((c->debug & D_ROUTES) || (p->debug & D_ROUTES))
 +  /* Propagate the route change */
 +  rte_announce(table, net, new_stored, old_stored,
 +      net->routes, old_best_stored);
 +
 +  return 1;
 +}
 +
 +int
 +channel_preimport(struct rt_import_request *req, rte *new, const rte *old)
 +{
 +  struct channel *c = SKIP_BACK(struct channel, in_req, req);
 +
 +  if (new && !old)
 +    if (CHANNEL_LIMIT_PUSH(c, RX))
 +      return 0;
 +
 +  if (!new && old)
 +    CHANNEL_LIMIT_POP(c, RX);
 +
 +  int new_in = new && !rte_is_filtered(new);
 +  int old_in = old && !rte_is_filtered(old);
++  
++  int verdict = 1;
 +
 +  if (new_in && !old_in)
 +    if (CHANNEL_LIMIT_PUSH(c, IN))
 +      if (c->in_keep & RIK_REJECTED)
-       {
 +      new->flags |= REF_FILTERED;
-       return 1;
-       }
 +      else
-       return 0;
++      verdict = 0;
 +
 +  if (!new_in && old_in)
 +    CHANNEL_LIMIT_POP(c, IN);
 +
-   return 1;
++  mpls_rte_preimport(new_in ? new : NULL, old_in ? old : NULL);
++
++  return verdict;
 +}
 +
 +void
 +rte_update(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
 +{
 +  if (!c->in_req.hook)
 +  {
 +    log(L_WARN "%s.%s: Called rte_update without import hook", c->proto->name, c->name);
 +    return;
 +  }
 +
 +  ASSERT(c->channel_state == CS_UP);
 +
 +  /* The import reloader requires prefilter routes to be the first layer */
 +  if (new && (c->in_keep & RIK_PREFILTER))
 +    if (ea_is_cached(new->attrs) && !new->attrs->next)
 +      new->attrs = ea_clone(new->attrs);
 +    else
 +      new->attrs = ea_lookup(new->attrs, 0);
 +
 +  const struct filter *filter = c->in_filter;
 +  struct channel_import_stats *stats = &c->import_stats;
 +
 +  if (new)
      {
 -      if (new_ok)
 -      rte_trace(c, new, '>', new == net->routes ? "added [best]" : "added");
 -      else if (old_ok)
 +      new->net = n;
 +
 +      int fr;
 +
 +      stats->updates_received++;
 +      if ((filter == FILTER_REJECT) ||
 +      ((fr = f_run(filter, new, 0)) > F_ACCEPT))
        {
 -        if (old != old_best)
 -          rte_trace(c, old, '>', "removed");
 -        else if (rte_is_ok(net->routes))
 -          rte_trace(c, old, '>', "removed [replaced]");
 +        stats->updates_filtered++;
 +        channel_rte_trace_in(D_FILTERS, c, new, "filtered out");
 +
 +        if (c->in_keep & RIK_REJECTED)
 +          new->flags |= REF_FILTERED;
          else
 -          rte_trace(c, old, '>', "removed [sole]");
 +          new = NULL;
 +      }
 +
++      if (new && c->proto->mpls_map)
++      mpls_handle_rte(c->proto->mpls_map, n, new);
++
 +      if (new)
 +      if (net_is_flow(n))
 +        rt_flowspec_resolve_rte(new, c);
 +      else
 +        rt_next_hop_resolve_rte(new);
 +
 +      if (new && !rte_validate(c, new))
 +      {
 +        channel_rte_trace_in(D_FILTERS, c, new, "invalid");
 +        stats->updates_invalid++;
 +        new = NULL;
        }
      }
 +  else
 +    stats->withdraws_received++;
  
 -  /* Propagate the route change */
 -  rte_announce(table, RA_UNDEF, net, new, old, net->routes, old_best);
 +  rte_import(&c->in_req, n, new, src);
 +
 +  /* Now the route attributes are kept by the in-table cached version
 +   * and we may drop the local handle */
 +  if (new && (c->in_keep & RIK_PREFILTER))
 +  {
 +    /* There may be some updates on top of the original attribute block */
 +    ea_list *a = new->attrs;
 +    while (a->next)
 +      a = a->next;
  
 -  if (!net->routes &&
 -      (table->gc_counter++ >= table->config->gc_threshold))
 -    rt_kick_prune_timer(table);
 +    ea_free(a);
 +  }
  
 -  if (old_ok && p->rte_remove)
 -    p->rte_remove(net, old);
 -  if (new_ok && p->rte_insert)
 -    p->rte_insert(net, new);
 +}
  
 -  if (old)
 +void
 +rte_import(struct rt_import_request *req, const net_addr *n, rte *new, struct rte_src *src)
 +{
 +  struct rt_import_hook *hook = req->hook;
 +  if (!hook)
 +  {
 +    log(L_WARN "%s: Called rte_import without import hook", req->name);
 +    return;
 +  }
 +
 +  RT_LOCKED(hook->table, tab)
 +  {
 +    net *nn;
 +    if (new)
      {
 -      if (!new)
 -      hmap_clear(&table->id_map, old->id);
 +      /* Use the actual struct network, not the dummy one */
 +      nn = net_get(tab, n);
 +      new->net = nn->n.addr;
 +      new->sender = hook;
  
 -      rte_free_quick(old);
 +      /* Set the stale cycle */
 +      new->stale_cycle = hook->stale_set;
      }
 +    else if (!(nn = net_find(tab, n)))
 +    {
 +      req->hook->stats.withdraws_ignored++;
 +      if (req->trace_routes & D_ROUTES)
 +      log(L_TRACE "%s > ignored %N withdraw", req->name, n);
 +      RT_RETURN(tab);
 +    }
 +
 +    /* Recalculate the best route */
 +    if (rte_recalculate(tab, hook, nn, new, src))
 +      ev_send(req->list, &hook->announce_event);
 +  }
  }
  
 -static int rte_update_nest_cnt;               /* Nesting counter to allow recursive updates */
 +/* Check rtable for best route to given net whether it would be exported do p */
 +int
 +rt_examine(rtable *tp, net_addr *a, struct channel *c, const struct filter *filter)
 +{
 +  rte rt = {};
  
 -static inline void
 -rte_update_lock(void)
 +  RT_LOCKED(tp, t)
 +  {
 +    net *n = net_find(t, a);
 +    if (n)
 +      rt = RTE_COPY_VALID(n->routes);
 +  }
 +
 +  if (!rt.src)
 +    return 0;
 +
 +  int v = c->proto->preexport ? c->proto->preexport(c, &rt) : 0;
 +  if (v == RIC_PROCESS)
 +    v = (f_run(filter, &rt, FF_SILENT) <= F_ACCEPT);
 +
 +  return v > 0;
 +}
 +
 +static void
 +rt_table_export_done(void *hh)
  {
 -  rte_update_nest_cnt++;
 +  struct rt_table_export_hook *hook = hh;
 +  struct rt_export_request *req = hook->h.req;
 +  void (*stopped)(struct rt_export_request *) = hook->h.stopped;
 +  rtable *t = SKIP_BACK(rtable, priv.exporter, hook->table);
 +
 +  RT_LOCKED(t, tab)
 +  {
 +    DBG("Export hook %p in table %s finished uc=%u\n", hook, tab->name, tab->use_count);
 +
 +    /* Drop pending exports */
 +    rt_export_used(&tab->exporter, hook->h.req->name, "stopped");
 +
 +    /* Do the common code; this frees the hook */
 +    rt_export_stopped(&hook->h);
 +  }
 +
 +  /* Report the channel as stopped. */
 +  CALL(stopped, req);
 +
 +  /* Unlock the table; this may free it */
 +  rt_unlock_table(t);
 +}
 +
 +void
 +rt_export_stopped(struct rt_export_hook *hook)
 +{
 +  /* Unlink from the request */
 +  hook->req->hook = NULL;
 +
 +  /* Unlist */
 +  rem_node(&hook->n);
 +
 +  /* Free the hook itself together with its pool */
 +  rp_free(hook->pool);
  }
  
  static inline void
@@@ -3469,35 -2445,22 +3473,32 @@@ rt_postconfig(struct config *c
   */
  
  void
 -rta_apply_hostentry(rta *a, struct hostentry *he, mpls_label_stack *mls)
 +ea_set_hostentry(ea_list **to, rtable *dep, rtable *src, ip_addr gw, ip_addr ll, u32 lnum, u32 labels[lnum])
 +{
-   struct {
-     struct adata ad;
-     struct hostentry *he;
-     u32 labels[0];
-   } *head = (void *) tmp_alloc_adata(sizeof *head + sizeof(u32) * lnum - sizeof(struct adata));
++  struct hostentry_adata *head = (struct hostentry_adata *) tmp_alloc_adata(
++      sizeof *head + sizeof(u32) * lnum - sizeof(struct adata));
 +
 +  RT_LOCKED(src, tab)
 +    head->he = rt_get_hostentry(tab, gw, ll, dep);
 +  memcpy(head->labels, labels, lnum * sizeof(u32));
 +
 +  ea_set_attr(to, EA_LITERAL_DIRECT_ADATA(
 +      &ea_gen_hostentry, 0, &head->ad));
 +}
 +
 +
 +static void
 +rta_apply_hostentry(struct rtable_private *tab UNUSED, ea_list **to, struct hostentry_adata *head)
  {
 -  a->hostentry = he;
 -  a->dest = he->dest;
 -  a->igp_metric = he->igp_metric;
 +  struct hostentry *he = head->he;
 +  u32 *labels = head->labels;
 +  u32 lnum = (u32 *) (head->ad.data + head->ad.length) - labels;
 +
 +  ea_set_attr_u32(to, &ea_gen_igp_metric, 0, he->igp_metric);
  
 -  if (a->dest != RTD_UNICAST)
 +  if (!he->src)
    {
 -    /* No nexthop */
 -no_nexthop:
 -    a->nh = (struct nexthop) {};
 -    if (mls)
 -    { /* Store the label stack for later changes */
 -      a->nh.labels_orig = a->nh.labels = mls->len;
 -      memcpy(a->nh.label, mls->stack, mls->len * sizeof(u32));
 -    }
 +    ea_set_dest(to, 0, RTD_UNREACHABLE);
      return;
    }
  
Simple merge
index f0ed85e2588b1e23c993ac3d91e4cc58e4173071,0d7788bb418d872ee5065527e1f4db59fed12914..f9ffbd01536626e0a4826c111b80ac6c5d40db9e
@@@ -894,17 -886,17 +895,18 @@@ main(int argc, char **argv
      dmalloc_debug(0x2f03d00);
  #endif
  
 -  parse_args(argc, argv);
 -  log_switch(1, NULL, NULL);
 -
 -  random_init();
 +  /* Prepare necessary infrastructure */
 +  the_bird_lock();
 +  times_update();
    resource_init();
 -  timer_init();
 +  random_init();
 +
 +  birdloop_init();
    olock_init();
 -  io_init();
    rt_init();
 +  io_init();
    if_init();
+   mpls_init();
  //  roa_init();
    config_init();
  
diff --cc test/bt-utils.c
index 53ee92d2b6485a3ed75a625494aee9f2605c92b1,a0353a2109192c8dfd7073e87ee354fd4ec5585c..56124e1f79c4f6fec039b112fb3b2642ee45570b
@@@ -60,12 -59,14 +61,13 @@@ bt_bird_init(void
  {
    if(bt_verbose)
      log_init_debug("");
 -  log_switch(bt_verbose != 0, NULL, NULL);
  
    olock_init();
 -  timer_init();
 -  io_init();
    rt_init();
 +  log_switch(1, NULL, NULL);
 +  io_init();
    if_init();
+   mpls_init();
    config_init();
  
    protos_build();