0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-11-18 17:18:42 +00:00

Route destination field merged with nexthop attribute; splitting flowspec validation result out.

As there is either a nexthop or another destination specification
(or othing in case of ROAs and Flowspec), it may be merged together.
This code is somehow quirky and should be replaced in future by better
implementation of nexthop.

Also flowspec validation result has its own attribute now as it doesn't
have anything to do with route nexthop.
This commit is contained in:
Maria Matejka 2022-05-15 18:09:30 +02:00
parent 4fe9881d62
commit 950775f6fa
21 changed files with 250 additions and 213 deletions

View File

@ -530,20 +530,24 @@
STATIC_ATTR; STATIC_ATTR;
ACCESS_RTE; ACCESS_RTE;
ACCESS_EATTRS; ACCESS_EATTRS;
struct rta *rta = (*fs->rte)->attrs;
switch (sa.sa_code) switch (sa.sa_code)
{ {
case SA_NET: RESULT(sa.type, net, (*fs->rte)->net->n.addr); break; case SA_NET: RESULT(sa.type, net, (*fs->rte)->net->n.addr); break;
case SA_PROTO: RESULT(sa.type, s, (*fs->rte)->src->proto->name); break; case SA_PROTO: RESULT(sa.type, s, (*fs->rte)->src->proto->name); break;
case SA_DEST: RESULT(sa.type, i, rta->dest); break;
default: default:
{ {
struct eattr *nh_ea = ea_find(*fs->eattrs, &ea_gen_nexthop); struct eattr *nhea = ea_find(*fs->eattrs, &ea_gen_nexthop);
struct nexthop *nh = nh_ea ? &((struct nexthop_adata *) nh_ea->u.ptr)->nh : NULL; struct nexthop_adata *nhad = nhea ? (struct nexthop_adata *) nhea->u.ptr : NULL;
struct nexthop *nh = nhad ? &nhad->nh : NULL;
switch (sa.sa_code) switch (sa.sa_code)
{ {
case SA_DEST:
RESULT(sa.type, i, nhad ?
(NEXTHOP_IS_REACHABLE(nhad) ? RTD_UNICAST : nhad->dest)
: RTD_NONE);
break;
case SA_GW: case SA_GW:
RESULT(sa.type, ip, nh ? nh->gw : IPA_NONE); RESULT(sa.type, ip, nh ? nh->gw : IPA_NONE);
break; break;
@ -575,19 +579,6 @@
ARG_TYPE(1, sa.type); ARG_TYPE(1, sa.type);
f_rta_cow(fs); f_rta_cow(fs);
{
struct rta *rta = (*fs->rte)->attrs;
if (sa.sa_code == SA_DEST)
{
int i = v1.val.i;
if ((i != RTD_BLACKHOLE) && (i != RTD_UNREACHABLE) && (i != RTD_PROHIBIT))
runtime( "Destination can be changed only to blackhole, unreachable or prohibit" );
rta->dest = i;
ea_unset_attr(fs->eattrs, 1, &ea_gen_nexthop);
}
else
{ {
union { union {
struct nexthop_adata nha; struct nexthop_adata nha;
@ -606,6 +597,16 @@
switch (sa.sa_code) switch (sa.sa_code)
{ {
case SA_DEST:
{
int i = v1.val.i;
if ((i != RTD_BLACKHOLE) && (i != RTD_UNREACHABLE) && (i != RTD_PROHIBIT))
runtime( "Destination can be changed only to blackhole, unreachable or prohibit" );
nha.nha.dest = i;
nha.ad.length = NEXTHOP_DEST_SIZE;
break;
}
case SA_GW: case SA_GW:
{ {
struct eattr *nh_ea = ea_find(*fs->eattrs, &ea_gen_nexthop); struct eattr *nh_ea = ea_find(*fs->eattrs, &ea_gen_nexthop);
@ -618,7 +619,6 @@
if (!n || (n->scope == SCOPE_HOST)) if (!n || (n->scope == SCOPE_HOST))
runtime( "Invalid gw address" ); runtime( "Invalid gw address" );
rta->dest = RTD_UNICAST;
nha.nh = (struct nexthop) { nha.nh = (struct nexthop) {
.gw = ip, .gw = ip,
.iface = n->iface, .iface = n->iface,
@ -632,7 +632,6 @@
if (!ifa) if (!ifa)
runtime( "Invalid iface name" ); runtime( "Invalid iface name" );
rta->dest = RTD_UNICAST;
nha.nh = (struct nexthop) { nha.nh = (struct nexthop) {
.iface = ifa, .iface = ifa,
}; };
@ -666,15 +665,16 @@
int i = v1.val.i; int i = v1.val.i;
if (i < 1 || i > 256) if (i < 1 || i > 256)
runtime( "Setting weight value out of bounds" ); runtime( "Setting weight value out of bounds" );
if (rta->dest != RTD_UNICAST)
runtime( "Setting weight needs regular nexthop " );
struct eattr *nh_ea = ea_find(*fs->eattrs, &ea_gen_nexthop); struct eattr *nh_ea = ea_find(*fs->eattrs, &ea_gen_nexthop);
if (!nh_ea) if (!nh_ea)
runtime( "No nexthop to set weight on" ); runtime( "No nexthop to set weight on" );
struct nexthop_adata *nhax = (struct nexthop_adata *) struct nexthop_adata *nhad = (struct nexthop_adata *) nh_ea->u.ptr;
tmp_copy_adata(&((struct nexthop_adata *) nh_ea->u.ptr)->ad); if (!NEXTHOP_IS_REACHABLE(nhad))
runtime( "Setting weight needs regular nexthop " );
struct nexthop_adata *nhax = (struct nexthop_adata *) tmp_copy_adata(&nhad->ad);
/* Set weight on all next hops */ /* Set weight on all next hops */
NEXTHOP_WALK(nh, nhax) NEXTHOP_WALK(nh, nhax)
@ -697,7 +697,6 @@
a->fresh = 1; a->fresh = 1;
} }
} }
}
INST(FI_EA_GET, 0, 1) { /* Access to extended attributes */ INST(FI_EA_GET, 0, 1) { /* Access to extended attributes */
DYNAMIC_ATTR; DYNAMIC_ATTR;

View File

@ -76,9 +76,17 @@ struct nexthop {
/* For packing one into eattrs */ /* For packing one into eattrs */
struct nexthop_adata { struct nexthop_adata {
struct adata ad; struct adata ad;
/* There is either a set of nexthops or a special destination (RTD_*) */
union {
struct nexthop nh; 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 RNF_ONLINK 0x1 /* Gateway is onlink regardless of IP ranges */
@ -88,7 +96,6 @@ typedef struct rta {
u32 hash_key; /* Hash over important fields */ u32 hash_key; /* Hash over important fields */
struct ea_list *eattrs; /* Extended Attribute chain */ struct ea_list *eattrs; /* Extended Attribute chain */
u16 cached:1; /* Are attributes cached? */ u16 cached:1; /* Are attributes cached? */
u16 dest:4; /* Route destination type (RTD_...) */
} rta; } rta;
#define RTS_STATIC 1 /* Normal static route */ #define RTS_STATIC 1 /* Normal static route */
@ -109,7 +116,7 @@ typedef struct rta {
#define RTS_MAX 16 #define RTS_MAX 16
#define RTD_NONE 0 /* Undefined next hop */ #define RTD_NONE 0 /* Undefined next hop */
#define RTD_UNICAST 1 /* Next hop is neighbor router */ #define RTD_UNICAST 1 /* A standard next hop */
#define RTD_BLACKHOLE 2 /* Silently drop packets */ #define RTD_BLACKHOLE 2 /* Silently drop packets */
#define RTD_UNREACHABLE 3 /* Reject as unreachable */ #define RTD_UNREACHABLE 3 /* Reject as unreachable */
#define RTD_PROHIBIT 4 /* Administratively prohibited */ #define RTD_PROHIBIT 4 /* Administratively prohibited */
@ -120,10 +127,6 @@ extern const char * rta_dest_names[RTD_MAX];
static inline const char *rta_dest_name(uint n) static inline const char *rta_dest_name(uint n)
{ return (n < RTD_MAX) ? rta_dest_names[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 * Extended Route Attributes
@ -331,9 +334,24 @@ extern struct ea_class ea_gen_source;
static inline u32 rt_get_source_attr(rte *rt) static inline u32 rt_get_source_attr(rte *rt)
{ return ea_get_int(rt->attrs->eattrs, &ea_gen_source, 0); } { return ea_get_int(rt->attrs->eattrs, &ea_gen_source, 0); }
/* Flowspec validation result */
#define FLOWSPEC_UNKNOWN 0
#define FLOWSPEC_VALID 1
#define FLOWSPEC_INVALID 2
extern struct ea_class ea_gen_flowspec_valid;
static inline u32 rt_get_flowspec_valid(rte *rt)
{ return ea_get_int(rt->attrs->eattrs, &ea_gen_flowspec_valid, FLOWSPEC_UNKNOWN); }
/* Next hop: For now, stored as adata */ /* Next hop: For now, stored as adata */
extern struct ea_class ea_gen_nexthop; 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 */ /* Next hop structures */
#define NEXTHOP_ALIGNMENT (_Alignof(struct nexthop)) #define NEXTHOP_ALIGNMENT (_Alignof(struct nexthop))
@ -359,7 +377,35 @@ struct nexthop_adata *nexthop_merge(struct nexthop_adata *x, struct nexthop_adat
struct nexthop_adata *nexthop_sort(struct nexthop_adata *x, linpool *lp); struct nexthop_adata *nexthop_sort(struct nexthop_adata *x, linpool *lp);
int nexthop_is_sorted(struct nexthop_adata *x); 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->eattrs, &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(rte *r)
{
return nhea_dest(ea_find(r->attrs->eattrs, &ea_gen_nexthop));
}
void rta_init(void); void rta_init(void);
#define rta_size(...) (sizeof(rta)) #define rta_size(...) (sizeof(rta))

View File

@ -66,6 +66,7 @@ enum btype {
T_ENUM_BGP_ORIGIN = 0x11, /* BGP Origin enum */ T_ENUM_BGP_ORIGIN = 0x11, /* BGP Origin enum */
T_ENUM_RA_PREFERENCE = 0x13, /* RA Preference enum */ T_ENUM_RA_PREFERENCE = 0x13, /* RA Preference enum */
T_ENUM_FLOWSPEC_VALID = 0x15, /* Flowspec validation result */
#define EAF_TYPE__MAX 0x1f #define EAF_TYPE__MAX 0x1f
#define EAF_EMBEDDED 0x01 /* Data stored in eattr.u.data (part of type spec) */ #define EAF_EMBEDDED 0x01 /* Data stored in eattr.u.data (part of type spec) */

View File

@ -133,7 +133,7 @@ CF_KEYWORDS(IPV4, IPV4_MC, IPV4_MPLS, IPV6, IPV6_MC, IPV6_MPLS, IPV6_SADR, VPN4,
CF_ENUM(T_ENUM_RTS, RTS_, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIRECT, CF_ENUM(T_ENUM_RTS, RTS_, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIRECT,
RIP, OSPF, OSPF_IA, OSPF_EXT1, OSPF_EXT2, BGP, PIPE, BABEL) 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_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(T_ENUM_ROA, ROA_, UNKNOWN, VALID, INVALID)
CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6) CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6)

View File

@ -166,6 +166,12 @@ const char * rta_dest_names[RTD_MAX] = {
[RTD_PROHIBIT] = "prohibited", [RTD_PROHIBIT] = "prohibited",
}; };
struct ea_class ea_gen_flowspec_valid = {
.name = "flowspec_valid",
.type = T_ENUM_FLOWSPEC_VALID,
.readonly = 1,
};
pool *rta_pool; pool *rta_pool;
static slab *rta_slab; static slab *rta_slab;
@ -1246,20 +1252,13 @@ rta_alloc_hash(void)
static inline uint static inline uint
rta_hash(rta *a) rta_hash(rta *a)
{ {
u64 h; return ea_hash(a->eattrs);
mem_hash_init(&h);
#define BMIX(f) mem_hash_mix_num(&h, a->f);
BMIX(dest);
#undef MIX
return mem_hash_value(&h) ^ ea_hash(a->eattrs);
} }
static inline int static inline int
rta_same(rta *x, rta *y) rta_same(rta *x, rta *y)
{ {
return (x->dest == y->dest && return ea_same(x->eattrs, y->eattrs);
ea_same(x->eattrs, y->eattrs));
} }
static rta * static rta *
@ -1382,10 +1381,8 @@ rta_do_cow(rta *o, linpool *lp)
void void
rta_dump(rta *a) rta_dump(rta *a)
{ {
static char *rtd[] = { "", " DEV", " HOLE", " UNREACH", " PROHIBIT" }; debug("uc=%d h=%04x",
a->uc, a->hash_key);
debug("uc=%d %s h=%04x",
a->uc, rtd[a->dest], a->hash_key);
if (!a->cached) if (!a->cached)
debug(" !CACHED"); debug(" !CACHED");
if (a->eattrs) if (a->eattrs)
@ -1449,6 +1446,7 @@ rta_init(void)
ea_register_init(&ea_gen_source); ea_register_init(&ea_gen_source);
ea_register_init(&ea_gen_nexthop); ea_register_init(&ea_gen_nexthop);
ea_register_init(&ea_gen_hostentry); ea_register_init(&ea_gen_hostentry);
ea_register_init(&ea_gen_flowspec_valid);
} }
/* /*

View File

@ -82,10 +82,7 @@ dev_ifa_notify(struct proto *P, uint flags, struct ifa *ad)
/* Use iface ID as local source ID */ /* Use iface ID as local source ID */
struct rte_src *src = rt_get_source(P, ad->iface->index); struct rte_src *src = rt_get_source(P, ad->iface->index);
rta a0 = { rta a0 = {};
.dest = RTD_UNICAST,
};
struct nexthop_adata nhad = { struct nexthop_adata nhad = {
.nh = { .iface = ad->iface, }, .nh = { .iface = ad->iface, },
.ad = { .length = (void *) NEXTHOP_NEXT(&nhad.nh) - (void *) nhad.ad.data, }, .ad = { .length = (void *) NEXTHOP_NEXT(&nhad.nh) - (void *) nhad.ad.data, },

View File

@ -47,6 +47,7 @@ rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, int primary
void (*get_route_info)(struct rte *, byte *buf); void (*get_route_info)(struct rte *, byte *buf);
eattr *nhea = ea_find(a->eattrs, &ea_gen_nexthop); eattr *nhea = ea_find(a->eattrs, &ea_gen_nexthop);
struct nexthop_adata *nhad = nhea ? (struct nexthop_adata *) nhea->u.ptr : NULL; struct nexthop_adata *nhad = nhea ? (struct nexthop_adata *) nhea->u.ptr : NULL;
int dest = NEXTHOP_IS_REACHABLE(nhad) ? RTD_UNICAST : nhad->dest;
tm_format_time(tm, &config->tf_route, e->lastmod); tm_format_time(tm, &config->tf_route, e->lastmod);
ip_addr a_from = ea_get_ip(a->eattrs, &ea_gen_from, IPA_NONE); ip_addr a_from = ea_get_ip(a->eattrs, &ea_gen_from, IPA_NONE);
@ -68,10 +69,10 @@ rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, int primary
if (d->last_table != d->tab) if (d->last_table != d->tab)
rt_show_table(c, d); rt_show_table(c, d);
cli_printf(c, -1007, "%-20s %s [%s %s%s]%s%s", ia, rta_dest_name(a->dest), cli_printf(c, -1007, "%-20s %s [%s %s%s]%s%s", ia, rta_dest_name(dest),
e->src->proto->name, tm, from, primary ? (sync_error ? " !" : " *") : "", info); e->src->proto->name, tm, from, primary ? (sync_error ? " !" : " *") : "", info);
if (a->dest == RTD_UNICAST) if (dest == RTD_UNICAST)
NEXTHOP_WALK(nh, nhad) NEXTHOP_WALK(nh, nhad)
{ {
char mpls[MPLS_MAX_LABEL_STACK*12 + 5], *lsp = mpls; char mpls[MPLS_MAX_LABEL_STACK*12 + 5], *lsp = mpls;

View File

@ -699,7 +699,7 @@ rte_trace(struct channel *c, rte *e, int dir, char *msg)
{ {
log(L_TRACE "%s.%s %c %s %N %uL %uG %s", log(L_TRACE "%s.%s %c %s %N %uL %uG %s",
c->proto->name, c->name ?: "?", dir, msg, e->net->n.addr, e->src->private_id, e->src->global_id, c->proto->name, c->name ?: "?", dir, msg, e->net->n.addr, e->src->private_id, e->src->global_id,
rta_dest_name(e->attrs->dest)); rta_dest_name(rte_dest(e)));
} }
static inline void static inline void
@ -1177,26 +1177,17 @@ rte_validate(rte *e)
return 0; return 0;
} }
if (net_type_match(n->n.addr, NB_DEST) == !e->attrs->dest)
{
/* Exception for flowspec that failed validation */
if (net_is_flow(n->n.addr) && (e->attrs->dest == RTD_UNREACHABLE))
return 1;
log(L_WARN "Ignoring route %N with invalid dest %d received via %s",
n->n.addr, e->attrs->dest, e->sender->proto->name);
return 0;
}
eattr *nhea = ea_find(e->attrs->eattrs, &ea_gen_nexthop); eattr *nhea = ea_find(e->attrs->eattrs, &ea_gen_nexthop);
if ((!nhea) != (e->attrs->dest != RTD_UNICAST)) int dest = nhea_dest(nhea);
if (net_type_match(n->n.addr, NB_DEST) == !dest)
{ {
log(L_WARN "Ignoring route %N with destination %d and %snexthop received via %s", log(L_WARN "Ignoring route %N with invalid dest %d received via %s",
n->n.addr, e->attrs->dest, (nhea ? "" : "no "), e->sender->proto->name); n->n.addr, dest, e->sender->proto->name);
return 0; return 0;
} }
if ((e->attrs->dest == RTD_UNICAST) && if ((dest == RTD_UNICAST) &&
!nexthop_is_sorted((struct nexthop_adata *) nhea->u.ptr)) !nexthop_is_sorted((struct nexthop_adata *) nhea->u.ptr))
{ {
log(L_WARN "Ignoring unsorted multipath route %N received via %s", log(L_WARN "Ignoring unsorted multipath route %N received via %s",
@ -2431,25 +2422,26 @@ rta_apply_hostentry(rta *a, struct hostentry_adata *head)
u32 *labels = head->labels; u32 *labels = head->labels;
u32 lnum = (u32 *) (head->ad.data + head->ad.length) - labels; u32 lnum = (u32 *) (head->ad.data + head->ad.length) - labels;
a->dest = he->dest;
ea_set_attr_u32(&a->eattrs, &ea_gen_igp_metric, 0, he->igp_metric); ea_set_attr_u32(&a->eattrs, &ea_gen_igp_metric, 0, he->igp_metric);
if (a->dest != RTD_UNICAST) if (!he->src)
{ {
/* No nexthop */ ea_set_dest(&a->eattrs, 0, RTD_UNREACHABLE);
ea_unset_attr(&a->eattrs, 0, &ea_gen_nexthop);
return;
}
if (!lnum && he->nexthop_linkable)
{ /* Just link the nexthop chain, no label append happens. */
ea_copy_attr(&a->eattrs, he->src->eattrs, &ea_gen_nexthop);
return; return;
} }
eattr *he_nh_ea = ea_find(he->src->eattrs, &ea_gen_nexthop); eattr *he_nh_ea = ea_find(he->src->eattrs, &ea_gen_nexthop);
ASSERT_DIE(he_nh_ea);
struct nexthop_adata *nhad = (struct nexthop_adata *) he_nh_ea->u.ptr; struct nexthop_adata *nhad = (struct nexthop_adata *) he_nh_ea->u.ptr;
int idest = nhea_dest(he_nh_ea);
if ((idest != RTD_UNICAST) ||
!lnum && he->nexthop_linkable)
{ /* Just link the nexthop chain, no label append happens. */
ea_copy_attr(&a->eattrs, he->src->eattrs, &ea_gen_nexthop);
return;
}
uint total_size = OFFSETOF(struct nexthop_adata, nh); uint total_size = OFFSETOF(struct nexthop_adata, nh);
@ -2467,10 +2459,14 @@ rta_apply_hostentry(rta *a, struct hostentry_adata *head)
if (total_size == OFFSETOF(struct nexthop_adata, nh)) if (total_size == OFFSETOF(struct nexthop_adata, nh))
{ {
a->dest = RTD_UNREACHABLE;
log(L_WARN "No valid nexthop remaining, setting route unreachable"); log(L_WARN "No valid nexthop remaining, setting route unreachable");
ea_unset_attr(&a->eattrs, 0, &ea_gen_nexthop); struct nexthop_adata nha = {
.ad.length = NEXTHOP_DEST_SIZE,
.dest = RTD_UNREACHABLE,
};
ea_set_attr_data(&a->eattrs, &ea_gen_nexthop, 0, &nha.ad.data, nha.ad.length);
return; return;
} }
@ -2511,19 +2507,28 @@ rta_apply_hostentry(rta *a, struct hostentry_adata *head)
static inline struct hostentry_adata * static inline struct hostentry_adata *
rta_next_hop_outdated(rta *a) rta_next_hop_outdated(rta *a)
{ {
/* First retrieve the hostentry */
eattr *heea = ea_find(a->eattrs, &ea_gen_hostentry); eattr *heea = ea_find(a->eattrs, &ea_gen_hostentry);
if (!heea) if (!heea)
return NULL; return NULL;
struct hostentry_adata *head = (struct hostentry_adata *) heea->u.ptr; struct hostentry_adata *head = (struct hostentry_adata *) heea->u.ptr;
if (!head->he->src) /* If no nexthop is present, we have to create one */
return (a->dest != RTD_UNREACHABLE) ? head : NULL;
eattr *he_nh_ea = ea_find(head->he->src->eattrs, &ea_gen_nexthop);
eattr *a_nh_ea = ea_find(a->eattrs, &ea_gen_nexthop); eattr *a_nh_ea = ea_find(a->eattrs, &ea_gen_nexthop);
if (!a_nh_ea)
return head;
return ((a->dest != head->he->dest) || struct nexthop_adata *nhad = (struct nexthop_adata *) a_nh_ea->u.ptr;
/* Shortcut for unresolvable hostentry */
if (!head->he->src)
return NEXTHOP_IS_REACHABLE(nhad) ? head : NULL;
/* Comparing our nexthop with the hostentry nexthop */
eattr *he_nh_ea = ea_find(head->he->src->eattrs, &ea_gen_nexthop);
return (
(ea_get_int(a->eattrs, &ea_gen_igp_metric, IGP_METRIC_UNKNOWN) != head->he->igp_metric) || (ea_get_int(a->eattrs, &ea_gen_igp_metric, IGP_METRIC_UNKNOWN) != head->he->igp_metric) ||
(!head->he->nexthop_linkable) || (!head->he->nexthop_linkable) ||
(!he_nh_ea != !a_nh_ea) || (!he_nh_ea != !a_nh_ea) ||
@ -2682,16 +2687,16 @@ rt_flowspec_update_rte(rtable *tab, rte *r)
const net_addr *n = r->net->n.addr; const net_addr *n = r->net->n.addr;
struct bgp_proto *p = (void *) r->src->proto; struct bgp_proto *p = (void *) r->src->proto;
int valid = rt_flowspec_check(bc->base_table, tab, n, r->attrs, p->is_interior); int valid = rt_flowspec_check(bc->base_table, tab, n, r->attrs, p->is_interior);
int dest = valid ? RTD_NONE : RTD_UNREACHABLE; int old = rt_get_flowspec_valid(r);
if (old == valid)
if (dest == r->attrs->dest)
return NULL; return NULL;
rta *a = alloca(RTA_MAX_SIZE); rta *a = alloca(RTA_MAX_SIZE);
memcpy(a, r->attrs, rta_size(r->attrs)); memcpy(a, r->attrs, rta_size(r->attrs));
a->dest = dest;
a->cached = 0; a->cached = 0;
ea_set_attr_u32(&a->eattrs, &ea_gen_flowspec_valid, 0, valid);
rte *new = sl_alloc(rte_slab); rte *new = sl_alloc(rte_slab);
memcpy(new, r, sizeof(rte)); memcpy(new, r, sizeof(rte));
new->attrs = rta_lookup(a); new->attrs = rta_lookup(a);
@ -3524,7 +3529,6 @@ rt_update_hostentry(rtable *tab, struct hostentry *he)
/* Reset the hostentry */ /* Reset the hostentry */
he->src = NULL; he->src = NULL;
he->dest = RTD_UNREACHABLE;
he->nexthop_linkable = 0; he->nexthop_linkable = 0;
he->igp_metric = 0; he->igp_metric = 0;
@ -3545,16 +3549,12 @@ rt_update_hostentry(rtable *tab, struct hostentry *he)
goto done; goto done;
} }
if (a->dest == RTD_UNICAST) eattr *nhea = ea_find(a->eattrs, &ea_gen_nexthop);
{ ASSERT_DIE(nhea);
eattr *ea = ea_find(a->eattrs, &ea_gen_nexthop); struct nexthop_adata *nhad = (void *) nhea->u.ptr;
if (!ea)
{
log(L_WARN "No nexthop in unicast route");
goto done;
}
NEXTHOP_WALK(nh, (struct nexthop_adata *) ea->u.ptr) if (NEXTHOP_IS_REACHABLE(nhad))
NEXTHOP_WALK(nh, nhad)
if (ipa_zero(nh->gw)) if (ipa_zero(nh->gw))
{ {
if (if_local_addr(he->addr, nh->iface)) if (if_local_addr(he->addr, nh->iface))
@ -3567,10 +3567,8 @@ rt_update_hostentry(rtable *tab, struct hostentry *he)
direct++; direct++;
} }
}
he->src = rta_clone(a); he->src = rta_clone(a);
he->dest = a->dest;
he->nexthop_linkable = !direct; he->nexthop_linkable = !direct;
he->igp_metric = rt_get_igp_metric(e); he->igp_metric = rt_get_igp_metric(e);
} }

View File

@ -144,7 +144,6 @@ struct hostentry {
unsigned hash_key; /* Hash key */ unsigned hash_key; /* Hash key */
unsigned uc; /* Use count */ unsigned uc; /* Use count */
struct rta *src; /* Source rta entry */ struct rta *src; /* Source rta entry */
byte dest; /* Chosen route destination type (RTD_...) */
byte nexthop_linkable; /* Nexthop list is completely non-device */ byte nexthop_linkable; /* Nexthop list is completely non-device */
u32 igp_metric; /* Chosen route IGP metric */ u32 igp_metric; /* Chosen route IGP metric */
}; };

View File

@ -677,10 +677,7 @@ babel_announce_rte(struct babel_proto *p, struct babel_entry *e)
} }
}; };
rta a0 = { rta a0 = { .eattrs = &eattrs.l, };
.dest = RTD_UNICAST,
.eattrs = &eattrs.l,
};
rta *a = rta_lookup(&a0); rta *a = rta_lookup(&a0);
rte *rte = rte_get_temp(a, p->p.main_source); rte *rte = rte_get_temp(a, p->p.main_source);
@ -691,12 +688,11 @@ babel_announce_rte(struct babel_proto *p, struct babel_entry *e)
else if (e->valid && (e->router_id != p->router_id)) else if (e->valid && (e->router_id != p->router_id))
{ {
/* Unreachable */ /* Unreachable */
rta a0 = { rta a0 = {};
.dest = RTD_UNREACHABLE,
};
ea_set_attr_u32(&a0.eattrs, &ea_gen_preference, 0, 1); ea_set_attr_u32(&a0.eattrs, &ea_gen_preference, 0, 1);
ea_set_attr_u32(&a0.eattrs, &ea_gen_source, 0, RTS_BABEL); ea_set_attr_u32(&a0.eattrs, &ea_gen_source, 0, RTS_BABEL);
ea_set_dest(&a0.eattrs, 0, RTD_UNREACHABLE);
rta *a = rta_lookup(&a0); rta *a = rta_lookup(&a0);
rte *rte = rte_get_temp(a, p->p.main_source); rte *rte = rte_get_temp(a, p->p.main_source);
@ -2263,9 +2259,13 @@ babel_kick_timer(struct babel_proto *p)
static int static int
babel_preexport(struct proto *P, struct rte *new) babel_preexport(struct proto *P, struct rte *new)
{ {
struct rta *a = new->attrs; if (new->src->proto != P)
return 0;
/* Reject our own unreachable routes */ /* Reject our own unreachable routes */
if ((a->dest == RTD_UNREACHABLE) && (new->src->proto == P)) eattr *ea = ea_find(new->attrs->eattrs, &ea_gen_nexthop);
struct nexthop_adata *nhad = (void *) ea->u.ptr;
if (!NEXTHOP_IS_REACHABLE(nhad))
return -1; return -1;
return 0; return 0;

View File

@ -1711,9 +1711,20 @@ bgp_preexport(struct proto *P, rte *e)
if (src == NULL) if (src == NULL)
return 0; return 0;
/* Reject flowspec that failed validation */ /* Reject flowspec that failed or are pending validation */
if ((e->attrs->dest == RTD_UNREACHABLE) && net_is_flow(e->net->n.addr)) if (net_is_flow(e->net->n.addr))
switch (rt_get_flowspec_valid(e))
{
case FLOWSPEC_VALID:
break;
case FLOWSPEC_INVALID:
return -1; return -1;
case FLOWSPEC_UNKNOWN:
if ((rt_get_source_attr(e) == RTS_BGP) &&
((struct bgp_channel *) e->sender)->base_table)
return -1;
break;
}
/* IBGP route reflection, RFC 4456 */ /* IBGP route reflection, RFC 4456 */
if (p->is_internal && src->is_internal && (p->local_as == src->local_as)) if (p->is_internal && src->is_internal && (p->local_as == src->local_as))

View File

@ -519,7 +519,9 @@ struct rte_source *bgp_get_source(struct bgp_proto *p, u32 path_id);
static inline int static inline int
rte_resolvable(rte *rt) rte_resolvable(rte *rt)
{ {
return rt->attrs->dest != RTD_UNREACHABLE; eattr *nhea = ea_find(rt->attrs->eattrs, &ea_gen_nexthop);
struct nexthop_adata *nhad = (void *) nhea->u.ptr;
return NEXTHOP_IS_REACHABLE(nhad) || (nhad->dest != RTD_UNREACHABLE);
} }

View File

@ -968,7 +968,6 @@ bgp_apply_next_hop(struct bgp_parse_state *s, rta *a, ip_addr gw, ip_addr ll)
ea_set_attr_u32(&a->eattrs, &ea_gen_igp_metric, 0, c->cf->cost); ea_set_attr_u32(&a->eattrs, &ea_gen_igp_metric, 0, c->cf->cost);
a->dest = RTD_UNICAST;
struct nexthop_adata nhad = { struct nexthop_adata nhad = {
.nh = { .nh = {
.gw = nbr->addr, .gw = nbr->addr,
@ -1003,8 +1002,7 @@ bgp_apply_mpls_labels(struct bgp_parse_state *s, rta *a, u32 lnum, u32 labels[ln
{ {
REPORT("Too many MPLS labels ($u)", lnum); REPORT("Too many MPLS labels ($u)", lnum);
a->dest = RTD_UNREACHABLE; ea_set_dest(&a->eattrs, 0, RTD_UNREACHABLE);
ea_unset_attr(&a->eattrs, 0, &ea_gen_nexthop);
return; return;
} }
@ -1039,15 +1037,21 @@ static void
bgp_apply_flow_validation(struct bgp_parse_state *s, const net_addr *n, rta *a) bgp_apply_flow_validation(struct bgp_parse_state *s, const net_addr *n, rta *a)
{ {
struct bgp_channel *c = s->channel; struct bgp_channel *c = s->channel;
int valid = rt_flowspec_check(c->base_table, c->c.table, n, a, s->proto->is_interior); uint valid = rt_flowspec_check(c->base_table, c->c.table, n, a, s->proto->is_interior);
a->dest = valid ? RTD_NONE : RTD_UNREACHABLE;
/* Invalidate cached rta if dest changes */ /* Invalidate cached rta */
if (s->cached_rta && (s->cached_rta->dest != a->dest)) if (s->cached_rta)
{ {
/* Has't changed */
if (valid == ea_get_int(s->cached_rta->eattrs, &ea_gen_flowspec_valid, FLOWSPEC_UNKNOWN))
return;
rta_free(s->cached_rta); rta_free(s->cached_rta);
s->cached_rta = NULL; s->cached_rta = NULL;
} }
/* Set the value */
ea_set_attr_u32(&a->eattrs, &ea_gen_flowspec_valid, 0, valid);
} }
static int static int
@ -1107,17 +1111,14 @@ bgp_use_gateway(struct bgp_export_state *s)
if (c->cf->next_hop_self && bgp_match_src(s, c->cf->next_hop_self)) if (c->cf->next_hop_self && bgp_match_src(s, c->cf->next_hop_self))
return NULL; return NULL;
/* Unreachable */
if (ra->dest != RTD_UNICAST)
return NULL;
eattr *nhea = ea_find(ra->eattrs, &ea_gen_nexthop); eattr *nhea = ea_find(ra->eattrs, &ea_gen_nexthop);
if (!nhea) if (!nhea)
return NULL; return NULL;
/* We need one valid global gateway */ /* We need one valid global gateway */
struct nexthop_adata *nhad = (struct nexthop_adata *) nhea->u.ptr; struct nexthop_adata *nhad = (struct nexthop_adata *) nhea->u.ptr;
if (!NEXTHOP_ONE(nhad) || ipa_zero(nhad->nh.gw) || if (!NEXTHOP_IS_REACHABLE(nhad) ||
!NEXTHOP_ONE(nhad) || ipa_zero(nhad->nh.gw) ||
ipa_is_link_local(nhad->nh.gw)) ipa_is_link_local(nhad->nh.gw))
return NULL; return NULL;

View File

@ -1983,8 +1983,7 @@ ort_changed(ort *nf, rta *nr)
if (!or || if (!or ||
(nf->n.metric1 != nf->old_metric1) || (nf->n.metric2 != nf->old_metric2) || (nf->n.metric1 != nf->old_metric1) || (nf->n.metric2 != nf->old_metric2) ||
(nf->n.tag != nf->old_tag) || (nf->n.rid != nf->old_rid) || (nf->n.tag != nf->old_tag) || (nf->n.rid != nf->old_rid))
(nr->dest != or->dest))
return 1; return 1;
eattr *nhea_n = ea_find(nr->eattrs, &ea_gen_nexthop); eattr *nhea_n = ea_find(nr->eattrs, &ea_gen_nexthop);
@ -2049,7 +2048,6 @@ again1:
if (nf->n.type) /* Add the route */ if (nf->n.type) /* Add the route */
{ {
rta a0 = { rta a0 = {
.dest = RTD_UNICAST,
}; };
struct { struct {

View File

@ -1366,16 +1366,9 @@ ospf_rt_notify(struct proto *P, struct channel *ch UNUSED, net *n, rte *new, rte
uint tag = ea_get_int(a->eattrs, &ea_ospf_tag, 0); uint tag = ea_get_int(a->eattrs, &ea_ospf_tag, 0);
ip_addr fwd = IPA_NONE; ip_addr fwd = IPA_NONE;
if (a->dest == RTD_UNICAST)
{
eattr *nhea = ea_find(a->eattrs, &ea_gen_nexthop); eattr *nhea = ea_find(a->eattrs, &ea_gen_nexthop);
if (!nhea) if (nhea)
{ {
log(L_ERR "%s: Unicast route without nexthop for %N",
p->p.name, n->n.addr);
return;
}
struct nexthop_adata *nhad = (struct nexthop_adata *) nhea->u.ptr; struct nexthop_adata *nhad = (struct nexthop_adata *) nhea->u.ptr;
if (use_gw_for_fwaddr(p, nhad->nh.gw, nhad->nh.iface)) if (use_gw_for_fwaddr(p, nhad->nh.gw, nhad->nh.iface))
fwd = nhad->nh.gw; fwd = nhad->nh.gw;

View File

@ -142,9 +142,7 @@ perf_loop(void *data)
*((net_addr_ip4 *) &(p->data[i].net)) = random_net_ip4(); *((net_addr_ip4 *) &(p->data[i].net)) = random_net_ip4();
if (!p->attrs_per_rte || !(i % p->attrs_per_rte)) { if (!p->attrs_per_rte || !(i % p->attrs_per_rte)) {
struct rta a0 = { struct rta a0 = {};
.dest = RTD_UNICAST,
};
ea_set_attr_u32(&a0.eattrs, &ea_gen_preference, 0, p->p.main_channel->preference); ea_set_attr_u32(&a0.eattrs, &ea_gen_preference, 0, p->p.main_channel->preference);
ea_set_attr_u32(&a0.eattrs, &ea_gen_source, 0, RTS_PERF); ea_set_attr_u32(&a0.eattrs, &ea_gen_source, 0, RTS_PERF);

View File

@ -151,9 +151,7 @@ rip_announce_rte(struct rip_proto *p, struct rip_entry *en)
if (rt) if (rt)
{ {
/* Update */ /* Update */
rta a0 = { rta a0 = {};
.dest = RTD_UNICAST,
};
struct { struct {
ea_list l; ea_list l;

View File

@ -120,9 +120,7 @@ rpki_table_add_roa(struct rpki_cache *cache, struct channel *channel, const net_
{ {
struct rpki_proto *p = cache->p; struct rpki_proto *p = cache->p;
rta a0 = { rta a0 = {};
.dest = RTD_NONE,
};
ea_set_attr_u32(&a0.eattrs, &ea_gen_preference, 0, channel->preference); ea_set_attr_u32(&a0.eattrs, &ea_gen_preference, 0, channel->preference);
ea_set_attr_u32(&a0.eattrs, &ea_gen_source, 0, RTS_RPKI); ea_set_attr_u32(&a0.eattrs, &ea_gen_source, 0, RTS_RPKI);

View File

@ -55,7 +55,6 @@ static_announce_rte(struct static_proto *p, struct static_route *r)
{ {
rta *a = allocz(RTA_MAX_SIZE); rta *a = allocz(RTA_MAX_SIZE);
struct rte_src *src = static_get_source(p, r->index); struct rte_src *src = static_get_source(p, r->index);
a->dest = r->dest;
ea_set_attr_u32(&a->eattrs, &ea_gen_preference, 0, p->p.main_channel->preference); ea_set_attr_u32(&a->eattrs, &ea_gen_preference, 0, p->p.main_channel->preference);
ea_set_attr_u32(&a->eattrs, &ea_gen_source, 0, RTS_STATIC); ea_set_attr_u32(&a->eattrs, &ea_gen_source, 0, RTS_STATIC);
@ -97,7 +96,7 @@ static_announce_rte(struct static_proto *p, struct static_route *r)
nhad->ad.data, (void *) nh - (void *) nhad->ad.data); 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; rtable *tab = ipa_is_ip4(r->via) ? p->igp_table_ip4 : p->igp_table_ip6;
u32 *labels = r->mls ? (void *) r->mls->data : NULL; u32 *labels = r->mls ? (void *) r->mls->data : NULL;
@ -107,6 +106,9 @@ static_announce_rte(struct static_proto *p, struct static_route *r)
r->via, IPA_NONE, lnum, labels); r->via, IPA_NONE, lnum, labels);
} }
else if (r->dest)
ea_set_dest(&a->eattrs, 0, r->dest);
/* Already announced */ /* Already announced */
if (r->state == SRS_CLEAN) if (r->state == SRS_CLEAN)
return; return;

View File

@ -1407,11 +1407,16 @@ HASH_DEFINE_REHASH_FN(RTH, struct krt_proto)
int int
krt_capable(rte *e) krt_capable(rte *e)
{ {
rta *a = e->attrs; eattr *ea = ea_find(e->attrs->eattrs, &ea_gen_nexthop);
if (!ea)
return 0;
switch (a->dest) struct nexthop_adata *nhad = (void *) ea->u.ptr;
if (NEXTHOP_IS_REACHABLE(nhad))
return 1;
switch (nhad->dest)
{ {
case RTD_UNICAST:
case RTD_BLACKHOLE: case RTD_BLACKHOLE:
case RTD_UNREACHABLE: case RTD_UNREACHABLE:
case RTD_PROHIBIT: case RTD_PROHIBIT:
@ -1591,7 +1596,7 @@ nl_add_rte(struct krt_proto *p, rte *e)
eattr *nhea = ea_find(a->eattrs, &ea_gen_nexthop); eattr *nhea = ea_find(a->eattrs, &ea_gen_nexthop);
struct nexthop_adata *nhad = nhea ? (struct nexthop_adata *) nhea->u.ptr : NULL; struct nexthop_adata *nhad = nhea ? (struct nexthop_adata *) nhea->u.ptr : NULL;
if (krt_ecmp6(p) && nhad && !NEXTHOP_ONE(nhad)) if (krt_ecmp6(p) && nhad && NEXTHOP_IS_REACHABLE(nhad) && !NEXTHOP_ONE(nhad))
{ {
uint cnt = 0; uint cnt = 0;
NEXTHOP_WALK(nh, nhad) NEXTHOP_WALK(nh, nhad)
@ -1616,7 +1621,8 @@ nl_add_rte(struct krt_proto *p, rte *e)
return err; return err;
} }
return nl_send_route(p, e, NL_OP_ADD, a->dest, nhad); return nl_send_route(p, e, NL_OP_ADD,
NEXTHOP_IS_REACHABLE(nhad) ? RTD_UNICAST : nhad->dest, nhad);
} }
static inline int static inline int
@ -1638,7 +1644,8 @@ nl_replace_rte(struct krt_proto *p, rte *e)
rta *a = e->attrs; rta *a = e->attrs;
eattr *nhea = ea_find(a->eattrs, &ea_gen_nexthop); eattr *nhea = ea_find(a->eattrs, &ea_gen_nexthop);
struct nexthop_adata *nhad = nhea ? (struct nexthop_adata *) nhea->u.ptr : NULL; struct nexthop_adata *nhad = nhea ? (struct nexthop_adata *) nhea->u.ptr : NULL;
return nl_send_route(p, e, NL_OP_REPLACE, a->dest, nhad); return nl_send_route(p, e, NL_OP_REPLACE,
NEXTHOP_IS_REACHABLE(nhad) ? RTD_UNICAST : nhad->dest, nhad);
} }
@ -1901,8 +1908,6 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h)
switch (i->rtm_type) switch (i->rtm_type)
{ {
case RTN_UNICAST: case RTN_UNICAST:
ra->dest = RTD_UNICAST;
if (a[RTA_MULTIPATH]) if (a[RTA_MULTIPATH])
{ {
struct nexthop_adata *nh = nl_parse_multipath(s, p, n, a[RTA_MULTIPATH], i->rtm_family, krt_src); struct nexthop_adata *nh = nl_parse_multipath(s, p, n, a[RTA_MULTIPATH], i->rtm_family, krt_src);
@ -1953,22 +1958,6 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h)
} }
} }
break;
case RTN_BLACKHOLE:
ra->dest = RTD_BLACKHOLE;
break;
case RTN_UNREACHABLE:
ra->dest = RTD_UNREACHABLE;
break;
case RTN_PROHIBIT:
ra->dest = RTD_PROHIBIT;
break;
/* FIXME: What about RTN_THROW? */
default:
SKIP("type %d\n", i->rtm_type);
return;
}
#ifdef HAVE_MPLS_KERNEL #ifdef HAVE_MPLS_KERNEL
if ((i->rtm_family == AF_MPLS) && a[RTA_NEWDST] && !a[RTA_MULTIPATH]) if ((i->rtm_family == AF_MPLS) && a[RTA_NEWDST] && !a[RTA_MULTIPATH])
nhad.nh.labels = rta_get_mpls(a[RTA_NEWDST], nhad.nh.label); nhad.nh.labels = rta_get_mpls(a[RTA_NEWDST], nhad.nh.label);
@ -1994,6 +1983,21 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h)
/* Finalize the nexthop */ /* Finalize the nexthop */
nhad.ad.length = (void *) NEXTHOP_NEXT(&nhad.nh) - (void *) nhad.ad.data; nhad.ad.length = (void *) NEXTHOP_NEXT(&nhad.nh) - (void *) nhad.ad.data;
break;
case RTN_BLACKHOLE:
nhad.nhad = NEXTHOP_DEST_LITERAL(RTD_BLACKHOLE);
break;
case RTN_UNREACHABLE:
nhad.nhad = NEXTHOP_DEST_LITERAL(RTD_UNREACHABLE);
break;
case RTN_PROHIBIT:
nhad.nhad = NEXTHOP_DEST_LITERAL(RTD_PROHIBIT);
break;
/* FIXME: What about RTN_THROW? */
default:
SKIP("type %d\n", i->rtm_type);
return;
}
if (i->rtm_scope != def_scope) if (i->rtm_scope != def_scope)
ea_set_attr(&ra->eattrs, ea_set_attr(&ra->eattrs,

View File

@ -608,17 +608,10 @@ krt_same_dest(rte *k, rte *e)
{ {
rta *ka = k->attrs, *ea = e->attrs; rta *ka = k->attrs, *ea = e->attrs;
if (ka->dest != ea->dest)
return 0;
if (ka->dest != RTD_UNICAST)
return 1;
eattr *nhea_k = ea_find(ka->eattrs, &ea_gen_nexthop); eattr *nhea_k = ea_find(ka->eattrs, &ea_gen_nexthop);
eattr *nhea_e = ea_find(ea->eattrs, &ea_gen_nexthop); eattr *nhea_e = ea_find(ea->eattrs, &ea_gen_nexthop);
ASSUME(nhea_k && nhea_e); return (!nhea_k == !nhea_e) && adata_same(nhea_k->u.ptr, nhea_e->u.ptr);
return adata_same(nhea_k->u.ptr, nhea_e->u.ptr);
} }
/* /*