]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Merge commit '0a729b50' into thread-next
authorMaria Matejka <mq@ucw.cz>
Sun, 29 Oct 2023 14:42:46 +0000 (15:42 +0100)
committerMaria Matejka <mq@ucw.cz>
Sun, 29 Oct 2023 14:42:46 +0000 (15:42 +0100)
This merge was particularly difficult. I finally resorted to delete the
symbol scope active flag altogether and replace its usage by other
means.

Also I had to update custom route attribute registration to fit
both the scope updates in v2 and the data model in v3.

15 files changed:
1  2 
conf/cf-lex.l
conf/conf.c
conf/conf.h
doc/bird.sgml
filter/config.Y
filter/decl.m4
filter/f-inst.c
filter/f-inst.h
filter/filter_test.c
lib/route.h
nest/cmds.c
nest/rt-attr.c
proto/bgp/attrs.c
proto/bgp/bgp.h
proto/bgp/config.Y

diff --cc conf/cf-lex.l
index c99bd7144208cd65c65bbaa512de6d12ed2c8548,28479ff381b56b372699273ca4b94ed4516dd527..2a25e6298eef4ef619ec4a1dd31199d5cb607254
@@@ -73,20 -73,9 +73,18 @@@ static uint cf_hash(const byte *c)
  
  HASH_DEFINE_REHASH_FN(SYM, struct symbol)
  
 -struct sym_scope *global_root_scope;
 +/* Global symbol scopes */
  pool *global_root_scope_pool;
  linpool *global_root_scope_linpool;
-     .active = 1,
 +static struct sym_scope
 +  global_root_scope = {
-     .active = 0,
 +  },
 +  global_filter_scope = {
 +    .next = &global_root_scope,
 +  };
 +
 +/* Local symbol scope: TODO this isn't thread-safe */
 +struct sym_scope *conf_this_scope;
  
  linpool *cfg_mem;
  
@@@ -634,7 -598,7 +632,8 @@@ cf_find_symbol_scope(const struct sym_s
  
    /* Find the symbol here or anywhere below */
    while (scope)
-     if (scope->active && scope->hash.data && (s = HASH_FIND(scope->hash, SYM, c)))
 -    if (scope->hash.data && (s = HASH_FIND(scope->hash, SYM, c)))
++    if (((scope != &global_filter_scope) || !new_config || new_config->allow_attributes) &&
++      scope->hash.data && (s = HASH_FIND(scope->hash, SYM, c)))
        return s;
      else
        scope = scope->next;
@@@ -727,37 -691,6 +726,33 @@@ cf_lex_symbol(const char *data
    }
  }
  
-   if (!global_filter_scope.hash.data)
-     return NULL;
-   struct symbol *sym = HASH_FIND(global_filter_scope.hash, SYM, name);
-   if (!sym || (sym->class != SYM_ATTRIBUTE))
-     return NULL;
-   else
-     return sym->attribute;
 +void
 +ea_lex_register(struct ea_class *def)
 +{
 +  def->sym = cf_root_symbol(def->name, &global_filter_scope);
 +  def->sym->class = SYM_ATTRIBUTE;
 +  def->sym->attribute = def;
 +}
 +
++#if 0
++/* When we start to support complete protocol removal, we may need this function */
 +void
 +ea_lex_unregister(struct ea_class *def)
 +{
 +  struct symbol *sym = def->sym;
 +  HASH_REMOVE2(global_filter_scope.hash, SYM, &root_pool, sym);
 +  mb_free(sym);
 +  def->sym = NULL;
 +}
++#endif
 +
 +struct ea_class *
 +ea_class_find_by_name(const char *name)
 +{
++  struct symbol *sym = cf_find_symbol_scope(config ? config->root_scope : &global_filter_scope, name);
++  return sym && (sym->class == SYM_ATTRIBUTE) ? sym->attribute : NULL;
 +}
 +
  void f_type_methods_register(void);
  
  /**
@@@ -828,7 -763,7 +822,6 @@@ cf_push_scope(struct config *conf, stru
  
    s->next = conf->current_scope;
    conf->current_scope = s;
--  s->active = 1;
    s->name = sym;
    s->slots = 0;
  }
@@@ -844,10 -779,10 +837,7 @@@ voi
  cf_pop_scope(struct config *conf)
  {
    ASSERT(!conf->current_scope->soft_scopes);
--
--  conf->current_scope->active = 0;
    conf->current_scope = conf->current_scope->next;
--
    ASSERT(conf->current_scope);
  }
  
@@@ -897,27 -832,6 +887,27 @@@ cf_swap_soft_scope(struct config *conf
    }
  }
  
-   ASSERT_DIE(!global_filter_scope.active);
-   global_filter_scope.active = 1;
 +/**
 + * cf_enter_filters - enable filter / route attributes namespace
 + */
 +void
 +cf_enter_filters(void)
 +{
-   ASSERT_DIE(global_filter_scope.active);
-   global_filter_scope.active = 0;
++  ASSERT_DIE(!new_config->allow_attributes);
++  new_config->allow_attributes = 1;
 +}
 +
 +/**
 + * cf_exit_filters - disable filter / route attributes namespace
 + */
 +void
 +cf_exit_filters(void)
 +{
++  ASSERT_DIE(new_config->allow_attributes);
++  new_config->allow_attributes = 0;
 +}
 +
 +
  /**
   * cf_symbol_class_name - get name of a symbol class
   * @sym: symbol
diff --cc conf/conf.c
index d828b6df994e1d9a166988aad7502a3221091a3f,b9239d9bedff7686e5f711822f5d30fae25dfee1..c76046ee6905aa686bb9abdea23bbdcda5afbb23
@@@ -60,7 -60,7 +60,8 @@@
  
  static jmp_buf conf_jmpbuf;
  
--struct config *config, *new_config;
++struct config *config;
++_Thread_local struct config *new_config;
  pool *config_pool;
  
  static struct config *old_config;     /* Old configuration */
diff --cc conf/conf.h
index b5168873bdc39d621f0726fd309a481250166e47,486499ad10a87ebbbb8ee68e18a548fec88b7a89..841d5c1fc798a55799f69ca848882daba5d47512
@@@ -58,8 -55,7 +58,9 @@@ struct config 
  
    struct sym_scope *root_scope;               /* Scope for root symbols */
    struct sym_scope *current_scope;    /* Current scope where we are actually in while parsing */
 -  int obstacle_count;                 /* Number of items blocking freeing of this config */
++  int allow_attributes;                       /* Allow attributes in the current state of configuration parsing */
 +  _Atomic int obstacle_count;         /* Number of items blocking freeing of this config */
 +  event done_event;                   /* Called when obstacle_count reaches zero */
    int shutdown;                               /* This is a pseudo-config for daemon shutdown */
    int gr_down;                                /* This is a pseudo-config for graceful restart */
    btime load_time;                    /* When we've got this configuration */
@@@ -67,7 -63,7 +68,7 @@@
  
  /* Please don't use these variables in protocols. Use proto_config->global instead. */
  extern struct config *config;         /* Currently active configuration */
--extern struct config *new_config;     /* Configuration being parsed */
++extern _Thread_local struct config *new_config;       /* Configuration being parsed */
  
  struct config *config_alloc(const char *name);
  int config_parse(struct config *);
@@@ -149,7 -145,7 +150,6 @@@ struct sym_scope 
  
    uint slots;                         /* Variable slots */
    byte soft_scopes;                   /* Number of soft scopes above */
--  byte active:1;                      /* Currently entered */
    byte block:1;                               /* No independent stack frame */
    byte readonly:1;                    /* Do not add new symbols */
  };
diff --cc doc/bird.sgml
Simple merge
diff --cc filter/config.Y
index 76e804361e50944e927f5298886c84e9a88072f3,dfabddf7bff52443cd011b0b8623f99ab4be76b0..f27d9f58bd209ca649d81244928cb7c126605749
@@@ -28,6 -28,6 +28,8 @@@ static struct f_method_scope 
  } f_method_scope_stack[32];
  static int f_method_scope_pos = -1;
  
++static struct sym_scope *f_for_stored_scope;
++
  #define FM  (f_method_scope_stack[f_method_scope_pos])
  
  static inline void f_method_call_start(struct f_inst *object)
    if (!scope)
      cf_error("No methods defined for type %s", f_type_name(object->type));
  
++  /* Replacing the current symbol scope with the appropriate method scope
++     for the given type. */
    FM = (struct f_method_scope) {
      .object = object,
      .main = new_config->current_scope,
      .scope = {
        .next = NULL,
        .hash = scope->hash,
--      .active = 1,
        .block = 1,
        .readonly = 1,
      },
  
  static inline void f_method_call_args(void)
  {
--  ASSERT_DIE(FM.scope.active);
--  FM.scope.active = 0;
--
++  /* For argument parsing, we need to revert back to the standard symbol scope. */
    new_config->current_scope = FM.main;
  }
  
  static inline void f_method_call_end(void)
  {
    ASSERT_DIE(f_method_scope_pos >= 0);
--  if (FM.scope.active) {
--    ASSERT_DIE(&FM.scope == new_config->current_scope);
++  if (&FM.scope == new_config->current_scope)
      new_config->current_scope = FM.main;
  
--    FM.scope.active = 0;
--  }
--
    f_method_scope_pos--;
  }
  
@@@ -423,14 -399,7 +420,18 @@@ filter_eval
  
  conf: custom_attr ;
  custom_attr: ATTRIBUTE type symbol ';' {
-   if (($3->class == SYM_ATTRIBUTE) && ($3->scope == new_config->root_scope))
-     cf_error("Duplicate attribute %s definition", $3->name);
-   cf_define_symbol(new_config, $3, SYM_ATTRIBUTE, attribute,
-       ea_register_alloc(new_config->pool, (struct ea_class) {
 -  cf_define_symbol(new_config, $3, SYM_ATTRIBUTE, attribute, ca_lookup(new_config->pool, $3->name, $2)->fda);
++  cf_enter_filters();
++  struct ea_class *ac = ea_class_find_by_name($3->name);
++  cf_exit_filters();
++  if (ac && (ac->type == $2))
++    ea_ref_class(new_config->pool, ac);
++  else
++    ac = ea_register_alloc(new_config->pool, (struct ea_class) {
 +      .name = $3->name,
 +      .type = $2,
-       })->class);
++    })->class;
++
++  cf_define_symbol(new_config, $3, SYM_ATTRIBUTE, attribute, ac);
  };
  
  conf: bt_test_suite ;
@@@ -1003,7 -958,7 +1004,15 @@@ cmd
       new_config->current_scope->slots += 2;
     } for_var IN
     /* Parse term in the parent scope */
--   { new_config->current_scope->active = 0; } term { new_config->current_scope->active = 1; }
++   {
++     ASSERT_DIE(f_for_stored_scope == NULL);
++     f_for_stored_scope = new_config->current_scope;
++     new_config->current_scope = new_config->current_scope->next;
++   } term {
++     ASSERT_DIE(f_for_stored_scope);
++     new_config->current_scope = f_for_stored_scope;
++     f_for_stored_scope = NULL;
++   }
     DO cmd {
       cf_pop_block_scope(new_config);
       $$ = f_for_cycle($3, $6, $9);
diff --cc filter/decl.m4
index 2cb09f28c9f2e7dabb9a1af1d646398f3c08d7f7,57bf94546bd6ebf32d69538c2061197db8ecc45b..0d3b83fe481fd49d0fa52c220bc452653f8a5a4d
@@@ -424,9 -423,9 +424,9 @@@ m4_undivert(112
    }
  
  FID_METHOD_SCOPE_INIT()m4_dnl
-   [INST_METHOD_OBJECT_TYPE] = { .active = 1, },
+   [INST_METHOD_OBJECT_TYPE] = {},
  FID_METHOD_REGISTER()m4_dnl
 -  method = lp_allocz(global_root_scope_linpool, sizeof(struct f_method) + INST_METHOD_NUM_ARGS * sizeof(enum f_type));
 +  method = lp_allocz(global_root_scope_linpool, sizeof(struct f_method) + INST_METHOD_NUM_ARGS * sizeof(enum btype));
    method->new_inst = f_new_method_]]INST_NAME()[[;
    method->arg_num = INST_METHOD_NUM_ARGS;
  m4_undivert(113)
diff --cc filter/f-inst.c
index 6baa1fcb3ec75eea08b017b82e89ea9a6f3184e7,a7bec81ed758331419f594f565b60deb9c8c86ae..8bbb04918f1cb6e094e952bc0f71deae4a5cbe45
  
    INST(FI_EA_SET, 1, 0) {
      ACCESS_RTE;
 -    ACCESS_EATTRS;
      ARG_ANY(1);
      DYNAMIC_ATTR;
 -    ARG_TYPE(1, da.f_type);
 +    ARG_TYPE(1, da->type);
      {
 -      struct ea_list *l = lp_alloc(fs->pool, sizeof(struct ea_list) + sizeof(eattr));
 -
 -      l->next = NULL;
 -      l->flags = EALF_SORTED;
 -      l->count = 1;
 -      l->attrs[0].id = da.ea_code;
 -      l->attrs[0].flags = da.flags;
 -      l->attrs[0].type = da.type;
 -      l->attrs[0].originated = 1;
 -      l->attrs[0].fresh = 1;
 -      l->attrs[0].undef = 0;
 -
 -      switch (da.type) {
 -      case EAF_TYPE_INT:
 -      case EAF_TYPE_ROUTER_ID:
 -      l->attrs[0].u.data = v1.val.i;
 -      break;
 +      struct eattr *a;
  
 -      case EAF_TYPE_IP_ADDRESS:;
 -      int len = sizeof(ip_addr);
 -      struct adata *ad = lp_alloc(fs->pool, sizeof(struct adata) + len);
 -      ad->length = len;
 -      (* (ip_addr *) ad->data) = v1.val.ip;
 -      l->attrs[0].u.ptr = ad;
 -      break;
 +      if (da->type >= EAF_TYPE__MAX)
 +      bug("Unsupported attribute type");
  
 -      case EAF_TYPE_OPAQUE:
 -      case EAF_TYPE_AS_PATH:
 -      case EAF_TYPE_INT_SET:
 -      case EAF_TYPE_EC_SET:
 -      case EAF_TYPE_LC_SET:
 -      l->attrs[0].u.ptr = v1.val.ad;
 +      switch (da->type) {
-       case T_OPAQUE:
 +      case T_IFACE:
++      case T_OPAQUE:
 +      runtime( "Setting opaque attribute is not allowed" );
        break;
  
 -      case EAF_TYPE_BITFIELD:
 -      {
 -        /* First, we have to find the old value */
 -        eattr *e = ea_find(*fs->eattrs, da.ea_code);
 -        u32 data = e ? e->u.data : 0;
 -
 -        if (v1.val.i)
 -          l->attrs[0].u.data = data | (1u << da.bit);
 -        else
 -          l->attrs[0].u.data = data & ~(1u << da.bit);
 -      }
 +      case T_IP:
 +      a = ea_set_attr(&fs->rte->attrs,
 +          EA_LITERAL_STORE_ADATA(da, 0, &v1.val.ip, sizeof(ip_addr)));
        break;
  
        default:
diff --cc filter/f-inst.h
index a476c998e834911634e83ff94787613a67aa57b6,955cfbdc76f43e0409ca35a43b76af0d7c04ce02..82767a929b9032e8a6d3e5214b20abbf8e542537
@@@ -107,15 -107,32 +107,16 @@@ void f_add_lines(const struct f_line_it
  
  struct filter *f_new_where(struct f_inst *);
  struct f_inst *f_dispatch_method(struct symbol *sym, struct f_inst *obj, struct f_inst *args, int skip);
 -struct f_inst *f_dispatch_method_x(const char *name, enum f_type t, struct f_inst *obj, struct f_inst *args);
 +struct f_inst *f_dispatch_method_x(const char *name, enum btype t, struct f_inst *obj, struct f_inst *args);
  struct f_inst *f_for_cycle(struct symbol *var, struct f_inst *term, struct f_inst *block);
 +struct f_inst *f_implicit_roa_check(struct rtable_config *tab);
  struct f_inst *f_print(struct f_inst *vars, int flush, enum filter_return fret);
  
 -static inline struct f_dynamic_attr f_new_dynamic_attr(u8 type, enum f_type f_type, uint code) /* Type as core knows it, type as filters know it, and code of dynamic attribute */
 -{ return (struct f_dynamic_attr) { .type = type, .f_type = f_type, .ea_code = code }; }   /* f_type currently unused; will be handy for static type checking */
 -static inline struct f_dynamic_attr f_new_dynamic_attr_bit(u8 bit, enum f_type f_type, uint code) /* Type as core knows it, type as filters know it, and code of dynamic attribute */
 -{ return (struct f_dynamic_attr) { .type = EAF_TYPE_BITFIELD, .bit = bit, .f_type = f_type, .ea_code = code }; }   /* f_type currently unused; will be handy for static type checking */
 -static inline struct f_static_attr f_new_static_attr(int f_type, int code, int readonly)
 -{ return (struct f_static_attr) { .f_type = f_type, .sa_code = code, .readonly = readonly }; }
 -
 -static inline int f_type_attr(int f_type) {
 -  switch (f_type) {
 -    case T_INT:               return EAF_TYPE_INT;
 -    case T_IP:                return EAF_TYPE_IP_ADDRESS;
 -    case T_QUAD:      return EAF_TYPE_ROUTER_ID;
 -    case T_PATH:      return EAF_TYPE_AS_PATH;
 -    case T_CLIST:     return EAF_TYPE_INT_SET;
 -    case T_ECLIST:    return EAF_TYPE_EC_SET;
 -    case T_LCLIST:    return EAF_TYPE_LC_SET;
 -    case T_BYTESTRING:        return EAF_TYPE_OPAQUE;
 -    default:
 -      cf_error("Custom route attribute of unsupported type");
 -  }
 -}
 +static inline struct f_static_attr f_new_static_attr(btype type, int code, int readonly)
 +{ return (struct f_static_attr) { .type = type, .sa_code = code, .readonly = readonly }; }
 +struct f_inst *f_generate_roa_check(struct rtable_config *table, struct f_inst *prefix, struct f_inst *asn);
 +
  /* Hook for call bt_assert() function in configuration */
  extern void (*bt_assert_hook)(int result, const struct f_line_item *assert);
  
index aa325aef2fd69b7aa8acf34ff569e315c9a1c6ea,782b3c029e83e4616a1d5e0b1845d105643900f2..65b2e4f0fb3480eab189cc5e17585efba307ffe5
@@@ -79,7 -78,9 +79,9 @@@ main(int argc, char *argv[]
    if (!bt_config_file_parse(BT_CONFIG_FILE))
      abort();
  
-   bt_test_suite_extra(t_reconfig, 0, BT_TIMEOUT, "Testing reconfiguration");
 -  bt_test_suite_arg(t_reconfig, BT_CONFIG_FILE ".overlay", "Testing reconfiguration to overlay");
 -  bt_test_suite_arg(t_reconfig, BT_CONFIG_FILE, "Testing reconfiguration back");
 -  bt_test_suite_arg(t_reconfig, BT_CONFIG_FILE, "Testing reconfiguration to the same file");
++  bt_test_suite_arg_extra(t_reconfig, BT_CONFIG_FILE ".overlay", 0, BT_TIMEOUT, "Testing reconfiguration to overlay");
++  bt_test_suite_arg_extra(t_reconfig, BT_CONFIG_FILE, 0, BT_TIMEOUT, "Testing reconfiguration back");
++  bt_test_suite_arg_extra(t_reconfig, BT_CONFIG_FILE, 0, BT_TIMEOUT, "Testing reconfiguration to the same file");
  
    struct f_bt_test_suite *t;
    WALK_LIST(t, config->tests)
diff --cc lib/route.h
index f0934f0615425446323add38e64f198c0c85828a,0000000000000000000000000000000000000000..cb1e3f4e30b206f0ee5124669833773fc8663328
mode 100644,000000..100644
--- /dev/null
@@@ -1,564 -1,0 +1,565 @@@
 +/*
 + *    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,
 +  };
 +}
 +
 +struct rte_src {
 +  struct rte_src *next;                       /* Hash chain */
 +  struct rte_owner *owner;            /* Route source owner */
 +  u32 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 *);
 +
 +/*
 + *    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_MAX 16
 +
 +#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;
 +
 +/* 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 nest/cmds.c
index 4805b5a24e89618d2908e4fb262faec1216cd7be,d49bbc53c32384454d7f38f8537142943adbe365..7984f0880412d6b989720753594e04c6e121053c
@@@ -54,9 -54,9 +54,6 @@@ cmd_show_symbols(struct sym_show_data *
      for (const struct sym_scope *scope = config->root_scope; scope; scope = scope->next)
        HASH_WALK(scope->hash, next, sym)
        {
--      if (!sym->scope->active)
--        continue;
--
        if (sd->type && (sym->class != sd->type))
          continue;
  
diff --cc nest/rt-attr.c
index 0457e68ad0b799a0cc509c32de59806fbd7654bd,d793c72e1ef8473e7f78ef75807af5697abc63fa..201dff30fba57db3f4ae5cfff59ed4e7bc1722a1
@@@ -562,135 -351,55 +562,141 @@@ nexthop_is_sorted(struct nexthop_adata 
    return 1;
  }
  
 -static inline slab *
 -nexthop_slab(struct nexthop *nh)
 +/*
 + *    Extended Attributes
 + */
 +
 +#define EA_CLASS_INITIAL_MAX  128
 +static struct ea_class **ea_class_global = NULL;
 +static uint ea_class_max;
 +static struct idm ea_class_idm;
 +
 +/* Config parser lex register function */
 +void ea_lex_register(struct ea_class *def);
- void ea_lex_unregister(struct ea_class *def);
 +
 +static void
 +ea_class_free(struct ea_class *cl)
  {
 -  return nexthop_slab_[MIN(nh->labels, 3)];
++  RTA_LOCK;
++
 +  /* No more ea class references. Unregister the attribute. */
 +  idm_free(&ea_class_idm, cl->id);
 +  ea_class_global[cl->id] = NULL;
-   if (!cl->hidden)
-     ea_lex_unregister(cl);
++
++  /* When we start supporting full protocol removal, we may need to call
++   * ea_lex_unregister(cl), see where ea_lex_register() is called. */
++
++  RTA_UNLOCK;
  }
  
 -static struct nexthop *
 -nexthop_copy(struct nexthop *o)
 +static void
 +ea_class_ref_free(resource *r)
  {
 -  struct nexthop *first = NULL;
 -  struct nexthop **last = &first;
 -
 -  for (; o; o = o->next)
 -    {
 -      struct nexthop *n = sl_allocz(nexthop_slab(o));
 -      n->gw = o->gw;
 -      n->iface = o->iface;
 -      n->next = NULL;
 -      n->flags = o->flags;
 -      n->weight = o->weight;
 -      n->labels_orig = o->labels_orig;
 -      n->labels = o->labels;
 -      for (int i=0; i<o->labels; i++)
 -      n->label[i] = o->label[i];
 -
 -      *last = n;
 -      last = &(n->next);
 -    }
 +  struct ea_class_ref *ref = SKIP_BACK(struct ea_class_ref, r, r);
 +  if (!--ref->class->uc)
 +    ea_class_free(ref->class);
 +}
  
 -  return first;
 +static void
 +ea_class_ref_dump(resource *r, unsigned indent UNUSED)
 +{
 +  struct ea_class_ref *ref = SKIP_BACK(struct ea_class_ref, r, r);
 +  debug("name \"%s\", type=%d\n", ref->class->name, ref->class->type);
  }
  
 +static struct resclass ea_class_ref_class = {
 +  .name = "Attribute class reference",
 +  .size = sizeof(struct ea_class_ref),
 +  .free = ea_class_ref_free,
 +  .dump = ea_class_ref_dump,
 +  .lookup = NULL,
 +  .memsize = NULL,
 +};
 +
  static void
 -nexthop_free(struct nexthop *o)
 +ea_class_init(void)
  {
 -  struct nexthop *n;
 +  ASSERT_DIE(ea_class_global == NULL);
  
 -  while (o)
 -    {
 -      n = o->next;
 -      sl_free(o);
 -      o = n;
 -    }
 +  idm_init(&ea_class_idm, rta_pool, EA_CLASS_INITIAL_MAX);
 +  ea_class_global = mb_allocz(rta_pool,
 +      sizeof(*ea_class_global) * (ea_class_max = EA_CLASS_INITIAL_MAX));
  }
  
- static struct ea_class_ref *
++struct ea_class_ref *
 +ea_ref_class(pool *p, struct ea_class *def)
 +{
 +  def->uc++;
 +  struct ea_class_ref *ref = ralloc(p, &ea_class_ref_class);
 +  ref->class = def;
 +  return ref;
 +}
  
 -/*
 - *    Extended Attributes
 - */
 +static struct ea_class_ref *
 +ea_register(pool *p, struct ea_class *def)
 +{
 +  def->id = idm_alloc(&ea_class_idm);
 +
 +  ASSERT_DIE(ea_class_global);
 +  while (def->id >= ea_class_max)
 +    ea_class_global = mb_realloc(ea_class_global, sizeof(*ea_class_global) * (ea_class_max *= 2));
 +
 +  ASSERT_DIE(def->id < ea_class_max);
 +  ea_class_global[def->id] = def;
 +
-   if (!def->hidden)
-     ea_lex_register(def);
 +  return ea_ref_class(p, def);
 +}
 +
 +struct ea_class_ref *
 +ea_register_alloc(pool *p, struct ea_class cl)
 +{
 +  struct ea_class_ref *ref;
 +
 +  RTA_LOCK;
 +  struct ea_class *clp = ea_class_find_by_name(cl.name);
 +  if (clp && clp->type == cl.type)
 +  {
 +    ref = ea_ref_class(p, clp);
 +    RTA_UNLOCK;
 +    return ref;
 +  }
 +
 +  uint namelen = strlen(cl.name) + 1;
 +
 +  struct {
 +    struct ea_class cl;
 +    char name[0];
 +  } *cla = mb_alloc(rta_pool, sizeof(struct ea_class) + namelen);
 +  cla->cl = cl;
 +  memcpy(cla->name, cl.name, namelen);
 +  cla->cl.name = cla->name;
 +
 +  ref = ea_register(p, &cla->cl);
 +  RTA_UNLOCK;
 +  return ref;
 +}
 +
 +void
 +ea_register_init(struct ea_class *clp)
 +{
 +  RTA_LOCK;
 +  ASSERT_DIE(!ea_class_find_by_name(clp->name));
-   ea_register(&root_pool, clp);
++
++  struct ea_class *def = ea_register(&root_pool, clp)->class;
++
++  if (!clp->hidden)
++    ea_lex_register(def);
++
 +  RTA_UNLOCK;
 +}
 +
 +struct ea_class *
 +ea_class_find_by_id(uint id)
 +{
 +  ASSERT_DIE(id < ea_class_max);
 +  ASSERT_DIE(ea_class_global[id]);
 +  return ea_class_global[id];
 +}
  
  static inline eattr *
  ea__find(ea_list *e, unsigned id)
index dfd6f1d3e8866c1b325896b7e1a7aa9fd517c48b,de45cae0e6181d90829f51e423c614795e6bb5c4..510456e8ce699e5db83bda9389cde9cb25c1778e
@@@ -1209,34 -1145,20 +1209,40 @@@ static union bgp_attr_desc bgp_attr_tab
    },
  };
  
 -static inline int
 -bgp_attr_known(uint code)
 +eattr *
 +bgp_find_attr(ea_list *attrs, uint code)
  {
 -  return (code < ARRAY_SIZE(bgp_attr_table)) && bgp_attr_table[code].name;
 +  return ea_find(attrs, BGP_EA_ID(code));
  }
  
 -void bgp_fix_attr_flags(ea_list *attrs)
 +void
 +bgp_register_attrs(void)
  {
 -  for (u8 i = 0; i < attrs->count; i++)
 +  for (uint i=0; i<ARRAY_SIZE(bgp_attr_table); i++)
    {
 -    attrs->attrs[i].flags = bgp_attr_table[EA_ID(attrs->attrs[i].id)].flags;
 +    if (!bgp_attr_table[i].name)
 +      bgp_attr_table[i] = (union bgp_attr_desc) {
 +      .name = mb_sprintf(&root_pool, "bgp_unknown_0x%02x", i),
-       .type = T_OPAQUE,
-       .flags = BAF_OPTIONAL,
++      .type = T_BYTESTRING,
++      .flags = BAF_OPTIONAL | BAF_TRANSITIVE,
 +      .readonly = 1,
 +      .export = bgp_export_unknown,
 +      .encode = bgp_encode_raw,
 +      .decode = bgp_decode_unknown,
 +      .format = bgp_format_unknown,
 +      };
 +
 +    ea_register_init(&bgp_attr_table[i].class);
    }
  }
  
++struct ea_class *
++bgp_find_ea_class_by_id(uint id)
++{
++  return (id < ARRAY_SIZE(bgp_attr_table)) ? &bgp_attr_table[id].class : NULL;
++}
++
 +
  /*
   *    Attribute export
   */
diff --cc proto/bgp/bgp.h
index 997c84560e069c271fce3174f42a371334e4bc70,c11433ecc68fd2c5f066aec17e9a49a18eae85cc..079a98ea2b14d3aaac7f0620d7dd692c97ac2129
@@@ -661,8 -650,6 +661,9 @@@ bgp_total_aigp_metric(const rte *e
    return metric;
  }
  
 +void bgp_register_attrs(void);
++struct ea_class *bgp_find_ea_class_by_id(uint id);
 +
  
  /* packets.c */
  
index 963bd724a623cadca3d55d11c7146a215a56944b,d9ff24d82b71f6ccecaab793a77ada3381ce80f1..b0a2df33712c0707e5e6fc8daba95891be17832d
@@@ -335,6 -332,47 +335,19 @@@ bgp_channel_end
  
  bgp_proto_channel: bgp_channel_start bgp_channel_opt_list bgp_channel_end;
  
 -
 -dynamic_attr: BGP_ORIGIN
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_ENUM_BGP_ORIGIN, EA_CODE(PROTOCOL_BGP, BA_ORIGIN)); } ;
 -dynamic_attr: BGP_PATH
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_AS_PATH, T_PATH, EA_CODE(PROTOCOL_BGP, BA_AS_PATH)); } ;
 -dynamic_attr: BGP_NEXT_HOP
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_IP_ADDRESS, T_IP, EA_CODE(PROTOCOL_BGP, BA_NEXT_HOP)); } ;
 -dynamic_attr: BGP_MED
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_CODE(PROTOCOL_BGP, BA_MULTI_EXIT_DISC)); } ;
 -dynamic_attr: BGP_LOCAL_PREF
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_CODE(PROTOCOL_BGP, BA_LOCAL_PREF)); } ;
 -dynamic_attr: BGP_ATOMIC_AGGR
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_OPAQUE, T_ENUM_EMPTY, EA_CODE(PROTOCOL_BGP, BA_ATOMIC_AGGR)); } ;
 -dynamic_attr: BGP_AGGREGATOR
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_OPAQUE, T_ENUM_EMPTY, EA_CODE(PROTOCOL_BGP, BA_AGGREGATOR)); } ;
 -dynamic_attr: BGP_COMMUNITY
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_INT_SET, T_CLIST, EA_CODE(PROTOCOL_BGP, BA_COMMUNITY)); } ;
 -dynamic_attr: BGP_ORIGINATOR_ID
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_ROUTER_ID, T_QUAD, EA_CODE(PROTOCOL_BGP, BA_ORIGINATOR_ID)); } ;
 -dynamic_attr: BGP_CLUSTER_LIST
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_INT_SET, T_CLIST, EA_CODE(PROTOCOL_BGP, BA_CLUSTER_LIST)); } ;
 -dynamic_attr: BGP_EXT_COMMUNITY
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_EC_SET, T_ECLIST, EA_CODE(PROTOCOL_BGP, BA_EXT_COMMUNITY)); } ;
 -dynamic_attr: BGP_AIGP
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_OPAQUE, T_ENUM_EMPTY, EA_CODE(PROTOCOL_BGP, BA_AIGP)); } ;
 -dynamic_attr: BGP_LARGE_COMMUNITY
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_LC_SET, T_LCLIST, EA_CODE(PROTOCOL_BGP, BA_LARGE_COMMUNITY)); } ;
 -dynamic_attr: BGP_OTC
 -      { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_CODE(PROTOCOL_BGP, BA_ONLY_TO_CUSTOMER)); } ;
 -
+ custom_attr: ATTRIBUTE BGP NUM type symbol ';' {
 -  if($3 > 255 || $3 < 1)
++  if ($3 > 255 || $3 < 1)
+     cf_error("Invalid attribute number. (Given %i, must be 1-255.)", $3);
 -  if($4 != T_BYTESTRING)
 -    cf_error("Attribute type must be bytestring, not %s.", f_type_name($4));
 -  struct f_dynamic_attr* a = (struct f_dynamic_attr*) malloc(sizeof(struct f_dynamic_attr));
 -  *a = f_new_dynamic_attr(f_type_attr($4), T_BYTESTRING, EA_CODE(PROTOCOL_BGP, $3));
 -  a->flags = BAF_TRANSITIVE | BAF_OPTIONAL;
 -  cf_define_symbol(new_config, $5, SYM_ATTRIBUTE, attribute, a);
++
++  struct ea_class *ac = bgp_find_ea_class_by_id($3);
++  ASSERT_DIE(ac);
++  if ($4 != ac->type)
++    cf_error("Attribute %d type must be %s, not %s.", $3, f_type_name(ac->type), f_type_name($4));
++
++  ea_ref_class(new_config->pool, ac);
++  cf_define_symbol(new_config, $5, SYM_ATTRIBUTE, attribute, ac);
+ };
  CF_ENUM(T_ENUM_BGP_ORIGIN, ORIGIN_, IGP, EGP, INCOMPLETE)
  
  CF_CODE