]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Merge commit '08571b20' into thread-merge-2.16
authorMaria Matejka <mq@ucw.cz>
Thu, 28 Nov 2024 07:22:06 +0000 (08:22 +0100)
committerMaria Matejka <mq@ucw.cz>
Thu, 28 Nov 2024 07:22:06 +0000 (08:22 +0100)
13 files changed:
1  2 
conf/confbase.Y
doc/bird.sgml
lib/a-set.c
lib/a-set_test.c
lib/attrs.h
lib/net.h
lib/route.h
nest/config.Y
nest/rt-attr.c
proto/bgp/attrs.c
proto/static/config.Y
proto/static/static.c
proto/static/static.h

diff --cc conf/confbase.Y
index 56dd15e7f74c8768b1e25c5ea5b056ef1c73d6b1,cdbdf1ce29e7bbc269401fc5e0211e88690cc86e..99fb3d7abb3a6dd6401bcf1ad1b4f08e268b9952
@@@ -121,11 -116,10 +121,11 @@@ CF_DECL
  
  %type <i> expr bool pxlen4
  %type <time> expr_us time
 -%type <a> ipa
 +%type <settle> settle
 +%type <a> ipa net_ip6_slash
  %type <net> net_ip4_ net_ip4 net_ip6_ net_ip6 net_ip_ net_ip net_or_ipa
- %type <net_ptr> net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_ net_roa_ net_ip6_sadr_ net_mpls_
+ %type <net_ptr> net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_ net_roa_ net_ip6_sadr_ net_mpls_ net_aspa_
 -%type <mls> label_stack_start label_stack
 +%type <ad> label_stack_start label_stack
  
  %type <t> text opttext
  %type <bs> bytestring
diff --cc doc/bird.sgml
Simple merge
diff --cc lib/a-set.c
Simple merge
index e401ba6be26372ac734c88c74a7cdd15a379f7c9,c52b1d4925c66aaad41491060f8744ab14521631..aa83c1755b7a3988bae594e7d0fc1ee7a4c391b2
@@@ -92,11 -92,11 +92,11 @@@ t_set_int_union(void
    const struct adata *set_union;
    set_union = int_set_union(tmp_linpool, set_sequence, set_sequence_same);
    bt_assert(int_set_get_size(set_union) == SET_SIZE);
-   bt_assert(int_set_format(set_union, 0, 2, buf, T_BUFFER_SIZE) == 0);
 -  bt_assert(int_set_format(set_union, ISF_ROUTER_ID, 2, buf, BUFFER_SIZE) == 0);
++  bt_assert(int_set_format(set_union, ISF_ROUTER_ID, 2, buf, T_BUFFER_SIZE) == 0);
  
    set_union = int_set_union(tmp_linpool, set_sequence, set_sequence_higher);
    bt_assert_msg(int_set_get_size(set_union) == SET_SIZE*2, "int_set_get_size(set_union) %d, SET_SIZE*2 %d", int_set_get_size(set_union), SET_SIZE*2);
-   bt_assert(int_set_format(set_union, 0, 2, buf, T_BUFFER_SIZE) == 0);
 -  bt_assert(int_set_format(set_union, ISF_ROUTER_ID, 2, buf, BUFFER_SIZE) == 0);
++  bt_assert(int_set_format(set_union, ISF_ROUTER_ID, 2, buf, T_BUFFER_SIZE) == 0);
  
    return 1;
  }
@@@ -106,17 -106,21 +106,21 @@@ t_set_int_format(void
  {
    generate_set_sequence(SET_TYPE_INT, SET_SIZE_FOR_FORMAT_OUTPUT);
  
-   bt_assert(int_set_format(set_sequence, 0, 0, buf, T_BUFFER_SIZE) == 0);
 -  bt_assert(int_set_format(set_sequence, ISF_ROUTER_ID, 0, buf, BUFFER_SIZE) == 0);
++  bt_assert(int_set_format(set_sequence, ISF_ROUTER_ID, 0, buf, T_BUFFER_SIZE) == 0);
    bt_assert(strcmp(buf, "0.0.0.0 0.0.0.1 0.0.0.2 0.0.0.3 0.0.0.4 0.0.0.5 0.0.0.6 0.0.0.7 0.0.0.8 0.0.0.9") == 0);
  
 -  bzero(buf, BUFFER_SIZE);
 -  bt_assert(int_set_format(set_sequence, ISF_ROUTER_ID, 2, buf, BUFFER_SIZE) == 0);
 +  bzero(buf, T_BUFFER_SIZE);
-   bt_assert(int_set_format(set_sequence, 0, 2, buf, T_BUFFER_SIZE) == 0);
++  bt_assert(int_set_format(set_sequence, ISF_ROUTER_ID, 2, buf, T_BUFFER_SIZE) == 0);
    bt_assert(strcmp(buf, "0.0.0.2 0.0.0.3 0.0.0.4 0.0.0.5 0.0.0.6 0.0.0.7 0.0.0.8 0.0.0.9") == 0);
  
 -  bzero(buf, BUFFER_SIZE);
 -  bt_assert(int_set_format(set_sequence, ISF_COMMUNITY_LIST, 0, buf, BUFFER_SIZE) == 0);
 +  bzero(buf, T_BUFFER_SIZE);
-   bt_assert(int_set_format(set_sequence, 1, 0, buf, T_BUFFER_SIZE) == 0);
++  bt_assert(int_set_format(set_sequence, ISF_COMMUNITY_LIST, 0, buf, T_BUFFER_SIZE) == 0);
    bt_assert(strcmp(buf, "(0,0) (0,1) (0,2) (0,3) (0,4) (0,5) (0,6) (0,7) (0,8) (0,9)") == 0);
  
 -  bzero(buf, BUFFER_SIZE);
 -  bt_assert(int_set_format(set_sequence, ISF_NUMBERS, 0, buf, BUFFER_SIZE) == 0);
++  bzero(buf, T_BUFFER_SIZE);
++  bt_assert(int_set_format(set_sequence, ISF_NUMBERS, 0, buf, T_BUFFER_SIZE) == 0);
+   bt_assert(strcmp(buf, "0 1 2 3 4 5 6 7 8 9") == 0);
    return 1;
  }
  
diff --cc lib/attrs.h
Simple merge
diff --cc lib/net.h
Simple merge
diff --cc lib/route.h
index 882e6fd483b82c193c2f049b819b5b7eac943b70,0000000000000000000000000000000000000000..b9bbd2ae0a9ad457bdfdb9cafde922e9e3e692d9
mode 100644,000000..100644
--- /dev/null
@@@ -1,642 -1,0 +1,646 @@@
 +/*
 + *    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
 +#undef EA_FREE_DEBUG
 +
 +#include "lib/type.h"
 +#include "lib/rcu.h"
 +#include "lib/hash.h"
 +#include "lib/event.h"
 +#include "lib/lockfree.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 (!) */ \
 +  byte flags;                         /* Table-specific flags */ \
 +  u8 stale_cycle;                     /* Auxiliary value for route refresh; may change in-table (!) */ \
 +
 +typedef struct rte {
 +  RTE_IN_TABLE_WRITABLE;
 +  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_OBSOLETE  16              /* Route is obsolete, pending propagation */
 +#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 */
 +  struct lfuc 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;
 +  u32 debug;
 +  event_list *list;
 +  event *prune;
 +  event *stop;
 +};
 +
 +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)
 +{
 +  lfuc_lock(&src->uc);
 +}
 +
 +static inline void rt_unlock_source(struct rte_src *src)
 +{
 +  lfuc_unlock(&src->uc, src->owner->list, src->owner->prune);
 +}
 +
 +#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;
 +  };
 +};
 +
 +/* For MPLS label stack generation */
 +struct nexthop_adata_mpls {
 +  struct nexthop_adata nhad;
 +  u32 label_space[MPLS_MAX_LABEL_STACK];
 +};
 +
 +#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_L3VPN 16                  /* MPLS L3VPN */
 +#define RTS_AGGREGATED 17             /* Aggregated route */
 +#define RTS_MAX 18
 +
 +#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 stored:5;                      /* enum ea_stored */
 +  byte rfu:3;
 +  word count;                         /* Number of attributes */
 +  eattr attrs[0];                     /* Attribute definitions themselves */
 +} ea_list;
 +
 +enum ea_stored {
 +  EALS_NONE = 0,                      /* This is a temporary ea_list */
 +  EALS_PREIMPORT = 1,                 /* State when route entered rte_update() */
 +  EALS_FILTERED = 2,                  /* State after filters */
 +  EALS_IN_TABLE = 3,                  /* State in table */
 +  EALS_KEY = 4,                               /* EA list used as key */
 +  EALS_CUSTOM = 0x10,                 /* OR this with custom values */
 +  EALS_MAX = 0x20,
 +};
 +
 +struct ea_storage {
 +  struct ea_storage *next_hash;               /* Next in hash chain */
 +  _Atomic u64 uc;                     /* Use count */
 +  u32 hash_key;                               /* List hash */
 +  PADDING(unused, 0, 4);              /* Sorry, we need u64 for the usecount */
 +  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_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 */ \
 +  u16 flags;                          /* Protocol-dependent flags */ \
 +  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 */ \
 +  struct f_val (*empty)(const struct ea_class *);     /* Return this instead of T_VOID as default value for filters */ \
 +
 +  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 && cls->type != T_PTR); \
 +    const eattr *ea = ea_find((_l), cls->id); \
 +    (ea ? ea->u.data : (_def)); \
 +    })
 +
 +#define ea_get_ptr(_l, _ident, _def)  ({ \
 +    struct ea_class *cls = ea_class_find((_ident)); \
 +    ASSERT_DIE(cls->type == T_PTR); \
 +    const eattr *ea = ea_find((_l), cls->id); \
 +    (ea ? ea->u.v_ptr : (_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)); \
 +    })
 +
 +#define ea_get_adata(_l, _ident)      ({ \
 +    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.ptr : &null_adata); \
 +    })
 +
 +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 attributes 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, u32 upto);
 +
 +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 && _type != T_PTR); \
 +    EA_LITERAL_GENERIC((_class)->id, _type, _flags, .u.i = _val); \
 +    })
 +
 +#define EA_LITERAL_STORE_PTR(_class, _flags, _ptr) ({ \
 +    btype _type = (_class)->type; \
 +    ASSERT_DIE(_type == T_PTR); \
 +    EA_LITERAL_GENERIC((_class)->id, _type, _flags, .u.v_ptr = _ptr); \
 +    })
 +
 +#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__ })
 +
 +#define EA_LITERAL_STORE_STRING(_class, _flags, string) ({EA_LITERAL_STORE_ADATA(_class, _flags, string, strlen(string)+1);})
 +
 +static inline eattr *
 +ea_set_attr(ea_list **to, eattr a)
 +{
 +  if (!a.id)
 +    bug("You have forgotten to register your EA class");
 +
 +  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_ptr(ea_list **to, const struct ea_class *def, uint flags, const void *data)
 +{ ea_set_attr(to, EA_LITERAL_STORE_PTR(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 == T_PTR)
 +      ea_set_attr_ptr(to, def, e->flags, (void *)e->u.v_ptr);
 +    else 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)
 +
 +static inline struct nexthop_adata *
 +rte_get_nexthops(rte *r)
 +{
 +  eattr *nhea = ea_find(r->attrs, &ea_gen_nexthop);
 +  return nhea ? SKIP_BACK(struct nexthop_adata, ad, nhea->u.ptr) : NULL;
 +}
 +
 +/* Route has regular, reachable nexthop (i.e. not RTD_UNREACHABLE and like) */
 +static inline int rte_is_reachable(rte *r)
 +{
 +  struct nexthop_adata *nhad = rte_get_nexthops(r);
 +  return nhad && 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));
 +}
 +
++/* ASPA Providers eattr */
++extern struct ea_class ea_gen_aspa_providers;
++
++
 +void rta_init(void);
 +
 +ea_list *ea_lookup_slow(ea_list *r, u32 squash_upto, enum ea_stored oid);
 +
 +static inline struct ea_storage *ea_get_storage(ea_list *r)
 +{
 +  ASSERT_DIE(r->stored);
 +  return SKIP_BACK(struct ea_storage, l[0], r);
 +}
 +
 +static inline ea_list *ea_ref(ea_list *r)
 +{
 +  ASSERT_DIE(0 < atomic_fetch_add_explicit(&ea_get_storage(r)->uc, 1, memory_order_acq_rel));
 +  return r;
 +}
 +
 +static inline ea_list *ea_lookup(ea_list *r, u32 squash_upto, enum ea_stored oid)
 +{
 +  ASSERT_DIE(oid);
 +  if ((r->stored == oid) || BIT32_TEST(&squash_upto, r->stored))
 +    return ea_ref(r);
 +  else
 +    return ea_lookup_slow(r, squash_upto, oid);
 +}
 +
 +struct ea_free_deferred {
 +  struct deferred_call dc;
 +  ea_list *attrs;
 +};
 +
 +void ea_free_deferred(struct deferred_call *dc);
 +
 +#ifdef EA_FREE_DEBUG
 +#define ea_free_later _ea_free_later_internal
 +#endif
 +
 +static inline ea_list *ea_free_later(ea_list *r)
 +{
 +  if (!r)
 +    return NULL;
 +
 +  struct ea_free_deferred efd = {
 +    .dc.hook = ea_free_deferred,
 +    .attrs = r,
 +  };
 +
 +  defer_call(&efd.dc, sizeof efd);
 +  return r;
 +}
 +
 +#ifdef EA_FREE_DEBUG
 +#undef ea_free_later
 +#define ea_free_later(x) ( log(L_INFO "EA free request %p at %s:%d", (x), __FILE__, __LINE__), _ea_free_later_internal(x) )
 +#endif
 +
 +#define ea_free ea_free_later
 +
 +static inline ea_list *ea_lookup_tmp(ea_list *r, u32 squash_upto, enum ea_stored oid)
 +{
 +  return ea_free_later(ea_lookup(r, squash_upto, oid));
 +}
 +
 +static inline ea_list *ea_ref_tmp(ea_list *r)
 +{
 +  ASSERT_DIE(r->stored);
 +  return ea_free_later(ea_ref(r));
 +}
 +
 +static inline ea_list *ea_strip_to(ea_list *r, u32 strip_to)
 +{
 +  ASSERT_DIE(strip_to);
 +  while (r && !BIT32_TEST(&strip_to, r->stored))
 +    r = r->next;
 +
 +  return r;
 +}
 +
 +void ea_dump(ea_list *);
 +void ea_dump_all(void);
 +void ea_show_list(struct cli *, ea_list *);
 +
 +#endif
diff --cc nest/config.Y
index 667a7e2c7d010864a1c369f00cc458f1545a7782,5d2f8d990361df7728183a65ed5f1132440f8962..760276efb0c72a6d273583dd56b523830733d34d
@@@ -151,9 -113,9 +151,9 @@@ proto_call_cmd_reload(struct proto_spe
  
  CF_DECLS
  
 -CF_KEYWORDS(ROUTER, ID, HOSTNAME, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED, DEBUG, ALL, OFF, DIRECT)
 +CF_KEYWORDS(ROUTER, ID, HOSTNAME, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED, DEBUG, ALL, OFF, DIRECT, PIPE)
  CF_KEYWORDS(INTERFACE, IMPORT, EXPORT, FILTER, NONE, VRF, DEFAULT, TABLE, TABLES, STATES, ROUTES, FILTERS)
- CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, MPLS)
+ CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, MPLS, ASPA)
  CF_KEYWORDS(RECEIVE, LIMIT, ACTION, WARN, BLOCK, RESTART, DISABLE, KEEP, FILTERED, RPKI)
  CF_KEYWORDS(PASSWORD, KEY, FROM, PASSIVE, TO, ID, EVENTS, PACKETS, PROTOCOLS, CHANNELS, INTERFACES)
  CF_KEYWORDS(ALGORITHM, KEYED, HMAC, MD5, SHA1, SHA256, SHA384, SHA512, BLAKE2S128, BLAKE2S256, BLAKE2B256, BLAKE2B512)
@@@ -164,11 -126,12 +164,12 @@@ 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, DIGEST, ROUTE, REFRESH, SETTLE, TIME, GC, THRESHOLD, PERIOD)
  CF_KEYWORDS(MPLS_LABEL, MPLS_POLICY, MPLS_CLASS)
+ CF_KEYWORDS(ASPA_PROVIDERS)
  
  /* 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_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, ASPA)
  
  CF_ENUM(T_ENUM_RTS, RTS_, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIRECT,
        RIP, OSPF, OSPF_IA, OSPF_EXT1, OSPF_EXT2, BGP, PIPE, BABEL, RPKI, L3VPN,
diff --cc nest/rt-attr.c
index 4ea5f76efe1bd69b08dc607614bf2e6a912754b0,0917663268ab69094778d574b4db673492fe891b..c68186bd509364b48f7b1d754ad7caffaa60fbb8
@@@ -176,26 -88,10 +176,38 @@@ const char * rta_dest_names[RTD_MAX] = 
    [RTD_PROHIBIT]      = "prohibited",
  };
  
 +struct ea_class ea_gen_flowspec_valid = {
 +  .name = "flowspec_valid",
 +  .type = T_ENUM_FLOWSPEC_VALID,
 +  .readonly = 1,
 +};
 +
 +const char * flowspec_valid_names[FLOWSPEC__MAX] = {
 +  [FLOWSPEC_UNKNOWN]  = "unknown",
 +  [FLOWSPEC_VALID]    = "",
 +  [FLOWSPEC_INVALID]  = "invalid",
 +};
 +
++static void
++ea_gen_aspa_providers_format(const eattr *a, byte *buf, uint size)
++{
++  int_set_format(a->u.ad, ISF_NUMBERS, -1, buf, size - 5);
++}
++
++struct ea_class ea_gen_aspa_providers = {
++  .name = "aspa_providers",
++  .type = T_CLIST,
++  .format = ea_gen_aspa_providers_format,
++};
++
 +DOMAIN(attrs) attrs_domain;
 +
  pool *rta_pool;
  
 -static slab *rta_slab_[4];
 -static slab *nexthop_slab_[4];
 +/* Assuming page size of 4096, these are magic values for slab allocation */
 +static const uint ea_slab_sizes[] = { 56, 112, 168, 288, 448, 800, 1344 };
 +static slab *ea_slab[ARRAY_SIZE(ea_slab_sizes)];
 +
  static slab *rte_src_slab;
  
  static struct idm src_ids;
@@@ -1423,84 -962,78 +1435,84 @@@ ea_show(struct cli *c, const eattr *e
    byte buf[CLI_MSG_SIZE];
    byte *pos = buf, *end = buf + sizeof(buf);
  
 -  if (EA_IS_CUSTOM(e->id))
 -    {
 -      const char *name = ea_custom_name(e->id);
 -      if (name)
 -        {
 -        pos += bsprintf(pos, "%s", name);
 -        status = GA_NAME;
 -      }
 -      else
 -      pos += bsprintf(pos, "%02x.", EA_PROTO(e->id));
 -    }
 -  else if (p = class_to_protocol[EA_PROTO(e->id)])
 -    {
 -      pos += bsprintf(pos, "%s.", p->name);
 -      if (p->get_attr)
 -      status = p->get_attr(e, pos, end - pos);
 -      pos += strlen(pos);
 -    }
 -  else if (EA_PROTO(e->id))
 -    pos += bsprintf(pos, "%02x.", EA_PROTO(e->id));
 -  else
 -    status = get_generic_attr(e, &pos, end - pos);
 +  ASSERT_DIE(e->id < ea_class_max);
  
 -  if (status < GA_NAME)
 -    pos += bsprintf(pos, "%02x", EA_ID(e->id));
 -  if (status < GA_FULL)
 -    {
 -      *pos++ = ':';
 -      *pos++ = ' ';
 +  struct ea_class *cls = ea_class_global[e->id];
 +  ASSERT_DIE(cls);
 +
 +  if (e->undef || cls->hidden)
 +    return;
 +  else if (cls->format)
 +    cls->format(e, buf, end - buf);
 +  else
 +    switch (e->type)
 +      {
 +      case T_INT:
 +        if ((cls == &ea_gen_igp_metric) && e->u.data >= IGP_METRIC_UNKNOWN)
 +          return;
  
 -      if (e->undef)
 -      bsprintf(pos, "undefined");
 -      else
 -      switch (e->type & EAF_TYPE_MASK)
 -      {
 -      case EAF_TYPE_INT:
          bsprintf(pos, "%u", e->u.data);
          break;
 -      case EAF_TYPE_OPAQUE:
 +      case T_OPAQUE:
          opaque_format(ad, pos, end - pos);
          break;
 -      case EAF_TYPE_IP_ADDRESS:
 +      case T_IP:
          bsprintf(pos, "%I", *(ip_addr *) ad->data);
          break;
 -      case EAF_TYPE_ROUTER_ID:
 +      case T_QUAD:
          bsprintf(pos, "%R", e->u.data);
          break;
 -      case EAF_TYPE_AS_PATH:
 +      case T_PATH:
          as_path_format(ad, pos, end - pos);
          break;
 -      case EAF_TYPE_BITFIELD:
 -        bsprintf(pos, "%08x", e->u.data);
 -        break;
 -      case EAF_TYPE_INT_SET:
 -        ea_show_int_set(c, ad, ISF_COMMUNITY_LIST, pos, buf, end);
 +      case T_CLIST:
-         ea_show_int_set(c, cls->name, ad, 1, buf);
++        ea_show_int_set(c, cls->name, ad, ISF_COMMUNITY_LIST, buf);
          return;
 -      case EAF_TYPE_EC_SET:
 -        ea_show_ec_set(c, ad, pos, buf, end);
 +      case T_ECLIST:
 +        ea_show_ec_set(c, cls->name, ad, buf);
          return;
 -      case EAF_TYPE_LC_SET:
 -        ea_show_lc_set(c, ad, pos, buf, end);
 +      case T_LCLIST:
 +        ea_show_lc_set(c, cls->name, ad, buf);
          return;
 -      case EAF_TYPE_STRING:
 +      case T_STRING:
          bsnprintf(pos, end - pos, "%s", (const char *) ad->data);
          break;
 +      case T_NEXTHOP_LIST:
 +        ea_show_nexthop_list(c, (struct nexthop_adata *) e->u.ptr);
 +        return;
 +      case T_HOSTENTRY:
 +        ea_show_hostentry(ad, pos, end - pos);
 +        break;
        default:
          bsprintf(pos, "<type %02x>", e->type);
 -      }
 -    }
 +      }
 +
 +  cli_printf(c, -1012, "\t%s: %s", cls->name, buf);
 +}
 +
 +static void
 +nexthop_dump(const struct adata *ad)
 +{
 +  struct nexthop_adata *nhad = (struct nexthop_adata *) ad;
  
 -  if (status != GA_HIDDEN)
 -    cli_printf(c, -1012, "\t%s", buf);
 +  debug(":");
 +
 +  if (!NEXTHOP_IS_REACHABLE(nhad))
 +  {
 +    const char *name = rta_dest_name(nhad->dest);
 +    if (name)
 +      debug(" %s", name);
 +    else
 +      debug(" D%d", nhad->dest);
 +  }
 +  else NEXTHOP_WALK(nh, nhad)
 +    {
 +      if (ipa_nonzero(nh->gw)) debug(" ->%I", nh->gw);
 +      if (nh->labels) debug(" L %d", nh->label[0]);
 +      for (int i=1; i<nh->labels; i++)
 +      debug("/%d", nh->label[i]);
 +      debug(" [%s]", nh->iface ? nh->iface->name : "???");
 +    }
  }
  
  /**
@@@ -1845,37 -1390,20 +1857,40 @@@ ea_show_list(struct cli *c, ea_list *ea
  void
  rta_init(void)
  {
 -  rta_pool = rp_new(&root_pool, "Attributes");
 +  attrs_domain = DOMAIN_NEW(attrs);
  
 -  rta_slab_[0] = sl_new(rta_pool, sizeof(rta));
 -  rta_slab_[1] = sl_new(rta_pool, sizeof(rta) + sizeof(u32));
 -  rta_slab_[2] = sl_new(rta_pool, sizeof(rta) + sizeof(u32)*2);
 -  rta_slab_[3] = sl_new(rta_pool, sizeof(rta) + sizeof(u32)*MPLS_MAX_LABEL_STACK);
 +  RTA_LOCK;
 +  rta_pool = rp_new(&root_pool, attrs_domain.attrs, "Attributes");
  
 -  nexthop_slab_[0] = sl_new(rta_pool, sizeof(struct nexthop));
 -  nexthop_slab_[1] = sl_new(rta_pool, sizeof(struct nexthop) + sizeof(u32));
 -  nexthop_slab_[2] = sl_new(rta_pool, sizeof(struct nexthop) + sizeof(u32)*2);
 -  nexthop_slab_[3] = sl_new(rta_pool, sizeof(struct nexthop) + sizeof(u32)*MPLS_MAX_LABEL_STACK);
 +  for (uint i=0; i<ARRAY_SIZE(ea_slab_sizes); i++)
 +    ea_slab[i] = sl_new(rta_pool, ea_slab_sizes[i]);
 +
 +  SPINHASH_INIT(rta_hash_table, RTAH, rta_pool, &global_work_list);
  
 -  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);
 +  ea_register_init(&ea_gen_hostentry_version);
 +
 +  /* 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);
++
++  /* ASPA providers */
++  ea_register_init(&ea_gen_aspa_providers);
  }
  
  /*
Simple merge
Simple merge
index cd393dd73a7039a05be26d1c4163876d79386889,661d04aa701a774d13cff4bfdcfaafac6a11057f..2bfeb8cc34e806ce9ade7d9fff1e9680c3a4c330
@@@ -84,34 -78,53 +84,54 @@@ static_announce_rte(struct static_prot
        nh->iface = r2->neigh->iface;
        nh->flags = r2->onlink ? RNF_ONLINK : 0;
        nh->weight = r2->weight;
 +
        if (r2->mls)
        {
 -      nh->labels = r2->mls->len;
 -      memcpy(nh->label, r2->mls->stack, r2->mls->len * sizeof(u32));
 +      nh->labels = r2->mls->length / sizeof(u32);
 +      memcpy(nh->label, r2->mls->data, r2->mls->length);
        }
  
 -      nexthop_insert(&nhs, nh);
 +      nh = NEXTHOP_NEXT(nh);
      }
  
 -    if (!nhs)
 -      goto withdraw;
 -
 -    nexthop_link(a, nhs);
 +    ea_set_attr_data(&ea, &ea_gen_nexthop, 0,
 +      nhad->ad.data, (void *) nh - (void *) nhad->ad.data);
    }
  
 -  if (r->dest == RTDX_RECURSIVE)
 +  else if (r->dest == RTDX_RECURSIVE)
    {
      rtable *tab = ipa_is_ip4(r->via) ? p->igp_table_ip4 : p->igp_table_ip6;
 -    rta_set_recursive_next_hop(p->p.main_channel->table, a, tab, r->via, IPA_NONE, r->mls);
 +    u32 *labels = r->mls ? (void *) r->mls->data : NULL;
 +    u32 lnum = r->mls ? r->mls->length / sizeof(u32) : 0;
 +
 +    ea_set_hostentry(&ea, p->p.main_channel->table, tab,
 +      r->via, IPA_NONE, lnum, labels);
 +
    }
  
 -    ea_list *ea = alloca(sizeof(ea_list) + sizeof(eattr));
 -    *ea = (ea_list) { .flags = EALF_SORTED, .count = 1, .next = a->eattrs, };
 -    ea->attrs[0] = (eattr) {
 -      .id = EA_ASPA_PROVIDERS,
 -      .type = EAF_TYPE_INT_SET,
 -      .u.ptr = r->aspa,
 -    };
 -    a->eattrs = ea;
 +  else if (r->dest)
 +    ea_set_dest(&ea, 0, r->dest);
 +
+   if (r->net->type == NET_ASPA)
+   {
+     if (r->dest != RTD_NONE)
+     {
+       log(L_WARN "%s: ASPA %u configured with nexthop, ignoring the nexthop",
+       p->p.name, ((struct net_addr_aspa *) r->net)->asn);
+       r->dest = RTD_NONE;
+     }
+     if (!r->aspa)
+     {
+       log(L_WARN "%s: ASPA %u configured with no provider list, ignoring the whole rule",
+       p->p.name, ((struct net_addr_aspa *) r->net)->asn);
+       goto withdraw;
+     }
++    ea_set_attr(&ea, EA_LITERAL_DIRECT_ADATA(
++      &ea_gen_aspa_providers, 0, r->aspa));
+   }
    if (p->p.mpls_channel)
    {
      struct mpls_channel *mc = (void *) p->p.mpls_channel;
index b7feb64990a49ac2cd05ad93996d57d383a74067,edd2dc228eb165a0f390d7d97253adbfec08ea07..22810a5e1be8e56b1fe7ea30121234e316d016a4
@@@ -50,7 -50,10 +50,10 @@@ struct static_route 
    byte use_bfd;                               /* Configured to use BFD */
    uint mpls_label;                    /* Local MPLS label, -1 if unused */
    struct bfd_request *bfd_req;                /* BFD request, if BFD is used */
-   struct adata *mls;                  /* MPLS label stack; may be NULL */
+   union {
 -    mpls_label_stack *mls;            /* MPLS label stack; may be NULL */
++    adata *mls;                               /* MPLS label stack; may be NULL */
+     adata *aspa;                      /* ASPA provider list; may be NULL */
+   };
  };
  
  /*