diff --git a/filter/config.Y b/filter/config.Y index 09d4fd89..5c3a1c16 100644 --- a/filter/config.Y +++ b/filter/config.Y @@ -364,7 +364,7 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN, TRUE, FALSE, RT, RO, UNKNOWN, GENERIC, FROM, GW, NET, PROTO, SOURCE, SCOPE, DEST, IFNAME, IFINDEX, WEIGHT, GW_MPLS, GW_MPLS_STACK, ONLINK, PREFERENCE, - ROA_CHECK, + ROA_CHECK, ASPA_CHECK, DEFINED, ADD, DELETE, RESET, PREPEND, @@ -946,6 +946,7 @@ term: | ROA_CHECK '(' rtable ')' { $$ = f_new_inst(FI_ROA_CHECK_IMPLICIT, $3); } | ROA_CHECK '(' rtable ',' term ',' term ')' { $$ = f_new_inst(FI_ROA_CHECK_EXPLICIT, $5, $7, $3); } + | ASPA_CHECK '(' rtable ',' term ')' { $$ = f_new_inst(FI_ASPA_CHECK_EXPLICIT, $5, $3); } | FORMAT '(' term ')' { $$ = f_new_inst(FI_FORMAT, $3); } diff --git a/filter/data.c b/filter/data.c index e268a8ec..282206eb 100644 --- a/filter/data.c +++ b/filter/data.c @@ -40,6 +40,7 @@ static const char * const f_type_str[] = { [T_ENUM_RTC] = "enum rtc", [T_ENUM_RTD] = "enum rtd", [T_ENUM_ROA] = "enum roa", + [T_ENUM_ASPA] = "enum aspa", [T_ENUM_NETTYPE] = "enum nettype", [T_ENUM_RA_PREFERENCE] = "enum ra_preference", [T_ENUM_AF] = "enum af", diff --git a/filter/data.h b/filter/data.h index df8d6a8f..bd1fa9a0 100644 --- a/filter/data.h +++ b/filter/data.h @@ -43,6 +43,7 @@ enum f_type { T_ENUM_RA_PREFERENCE = 0x37, T_ENUM_AF = 0x38, T_ENUM_MPLS_POLICY = 0x39, + T_ENUM_ASPA = 0x3a, /* new enums go here */ T_ENUM_EMPTY = 0x3f, /* Special hack for atomic_aggr */ diff --git a/filter/f-inst.c b/filter/f-inst.c index 6593a381..bebd13c5 100644 --- a/filter/f-inst.c +++ b/filter/f-inst.c @@ -1606,6 +1606,21 @@ } + INST(FI_ASPA_CHECK_EXPLICIT, 1, 1) { /* ASPA Check */ + NEVER_CONSTANT; + ARG(1, T_PATH); + RTC(2); + struct rtable *table = rtc->table; + + if (!table) + runtime("Missing ASPA table"); + + if (table->addr_type != NET_ASPA) + runtime("Table type must be ASPA"); + + RESULT(T_ENUM_ASPA, i, [[ aspa_check(table, v1.val.ad) ]]); + } + INST(FI_FROM_HEX, 1, 1) { /* Convert hex text to bytestring */ ARG(1, T_STRING); diff --git a/nest/config.Y b/nest/config.Y index 5d2f8d99..594ab3fc 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -139,6 +139,7 @@ CF_ENUM(T_ENUM_RTS, RTS_, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIRECT, 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_ROA, ROA_, UNKNOWN, VALID, INVALID) +CF_ENUM(T_ENUM_ASPA, ASPA_, UNKNOWN, VALID, INVALID) CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6) CF_ENUM(T_ENUM_MPLS_POLICY, MPLS_POLICY_, NONE, STATIC, PREFIX, AGGREGATE, VRF) diff --git a/nest/route.h b/nest/route.h index 9140b9e1..a17ae696 100644 --- a/nest/route.h +++ b/nest/route.h @@ -15,6 +15,7 @@ #include "lib/net.h" struct ea_list; +struct adata; struct protocol; struct proto; struct rte_src; @@ -320,6 +321,7 @@ static inline net *net_get(rtable *tab, const net_addr *addr) { return (net *) f net *net_get(rtable *tab, const net_addr *addr); net *net_route(rtable *tab, const net_addr *n); int net_roa_check(rtable *tab, const net_addr *n, u32 asn); +int aspa_check(rtable *tab, const struct adata *path); rte *rte_find(net *net, struct rte_src *src); rte *rte_get_temp(struct rta *, struct rte_src *src); void rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src); @@ -781,4 +783,9 @@ int rt_flowspec_check(rtable *tab_ip, rtable *tab_flow, const net_addr *n, rta * #define ROA_VALID 1 #define ROA_INVALID 2 +#define ASPA_UNKNOWN 0 +#define ASPA_VALID 1 +#define ASPA_INVALID 2 +#define ASPA_CONTAINS_CONFED 3 + #endif diff --git a/nest/rt-table.c b/nest/rt-table.c index 1b30e7dc..2b930914 100644 --- a/nest/rt-table.c +++ b/nest/rt-table.c @@ -346,6 +346,95 @@ net_roa_check(rtable *tab, const net_addr *n, u32 asn) #undef FW } +/** + * aspa_check - check validity of AS Path in an ASPA table + * @tab: ASPA table + * @path: AS Path to check + * + * Implements draft-ietf-sidrops-aspa-verification-16. + */ +int aspa_check(rtable *tab, const adata *path) +{ + struct lp_state lps; + lp_save(tmp_linpool, &lps); + + /* No support for confed paths */ + if (as_path_contains_confed(path)) + return ASPA_CONTAINS_CONFED; + + /* Normalize the AS Path: drop stuffings */ + uint len = as_path_getlen(path); + u32 *asns = alloca(sizeof(u32) * len); + uint ppos = 0; + int nsz = 0; + while (as_path_walk(path, &ppos, &asns[nsz])) + if ((nsz == 0) || (asns[nsz] != asns[nsz-1])) + nsz++; + + /* Find the provider blocks for every AS on the path + * and check allowed directions */ + bool *up = alloca(sizeof(bool) * nsz); + bool *down = alloca(sizeof(bool) * nsz); + bool unknown_flag = false; + + for (int ap=0; aproutes) + { + /* No ASPA for this ASN, therefore UNKNOWN */ + unknown_flag = up[ap] = down[ap] = true; + continue; + } + + up[ap] = down[ap] = false; + + for (rte *e = n->routes; e; e = e->next) + { + if (!rte_is_valid(e)) + continue; + + eattr *ea = ea_find(e->attrs->eattrs, EA_ASPA_PROVIDERS); + if (!ea) + continue; + + for (uint i=0; i * sizeof(u32) < ea->u.ptr->length; i++) + { + if ((ap > 0) && ((u32 *) ea->u.ptr->data)[i] == asns[ap-1]) + down[ap] = true; + if ((ap + 1 < nsz) && ((u32 *) ea->u.ptr->data)[i] == asns[ap+1]) + up[ap] = true; + + if (down[ap] || up[ap]) + goto peering_found; + } + } +peering_found:; + } + + /* Check whether the topology is first ramp up and then ramp down. */ + int up_end = 0; + while (up_end < nsz && up[up_end]) + up_end++; + + int down_end = nsz - 1; + while (down_end > 0 && down[down_end]) + down_end--; + + /* A significant overlap of obvious unknowns or misconfigured ASPAs. */ + if (up_end - down_end >= 2) + return ASPA_UNKNOWN; + + /* The path has either a single transit provider, or a peering pair on top */ + else if (up_end - down_end >= 0) + return unknown_flag ? ASPA_UNKNOWN : ASPA_VALID; + + /* There is a gap between valid ramp up and valid ramp down */ + else + return ASPA_INVALID; +} + /** * rte_find - find a route * @net: network node