From c5c9bd811b05df3675a3c549987ebc12d789d08d Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Thu, 25 Jan 2024 18:39:40 +0100 Subject: [PATCH] Filter: Ethernet and EVPN support --- filter/config.Y | 9 ++- filter/data.c | 6 ++ filter/data.h | 3 + filter/f-inst.c | 115 +++++++++++++++++++++++++++++++--- filter/test.conf | 101 +++++++++++++++++++++++++++++ proto/aggregator/aggregator.c | 3 + 6 files changed, 228 insertions(+), 9 deletions(-) diff --git a/filter/config.Y b/filter/config.Y index f3ed2dc5..a10703c2 100644 --- a/filter/config.Y +++ b/filter/config.Y @@ -126,6 +126,7 @@ f_valid_set_type(int type) case T_EC: case T_LC: case T_RD: + case T_MAC: return 1; default: @@ -357,7 +358,7 @@ CF_DECLS CF_KEYWORDS_EXCLUSIVE(IN) CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN, ACCEPT, REJECT, ERROR, - INT, BOOL, IP, PREFIX, RD, PAIR, QUAD, EC, LC, + INT, BOOL, IP, PREFIX, RD, MAC, PAIR, QUAD, EC, LC, SET, STRING, BYTESTRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST, IF, THEN, ELSE, CASE, FOR, DO, @@ -455,6 +456,7 @@ type: | BOOL { $$ = T_BOOL; } | IP { $$ = T_IP; } | RD { $$ = T_RD; } + | MAC { $$ = T_MAC; } | PREFIX { $$ = T_NET; } | PAIR { $$ = T_PAIR; } | QUAD { $$ = T_QUAD; } @@ -475,8 +477,9 @@ type: case T_QUAD: case T_EC: case T_LC: - case T_RD: case T_IP: + case T_RD: + case T_MAC: $$ = T_SET; break; @@ -640,6 +643,7 @@ set_atom: NUM { $$.type = T_INT; $$.val.i = $1; } | fipa { $$ = $1; } | VPN_RD { $$.type = T_RD; $$.val.ec = $1; } + | MAC_ { $$.type = T_MAC; $$.val.mac = $1; } | ENUM { $$.type = pair_a($1); $$.val.i = pair_b($1); } | '(' term ')' { $$ = cf_eval($2, T_VOID); @@ -797,6 +801,7 @@ constant: | BYTETEXT { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BYTESTRING, .val.bs = $1, }); } | fipa { $$ = f_new_inst(FI_CONSTANT, $1); } | VPN_RD { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_RD, .val.ec = $1, }); } + | MAC_ { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_MAC, .val.mac = $1, }); } | net_ { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_NET, .val.net = $1, }); } | '[' ']' { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_SET, .val.t = NULL, }); } | '[' set_items ']' { diff --git a/filter/data.c b/filter/data.c index e268a8ec..fd47d024 100644 --- a/filter/data.c +++ b/filter/data.c @@ -43,6 +43,8 @@ static const char * const f_type_str[] = { [T_ENUM_NETTYPE] = "enum nettype", [T_ENUM_RA_PREFERENCE] = "enum ra_preference", [T_ENUM_AF] = "enum af", + [T_ENUM_MPLS_POLICY] = "enum mpls_policy", + [T_ENUM_NET_EVPN] = "enum net_evpn", [T_IP] = "ip", [T_NET] = "prefix", @@ -56,6 +58,7 @@ static const char * const f_type_str[] = { [T_LC] = "lc", [T_LCLIST] = "lclist", [T_RD] = "rd", + [T_MAC] = "mac", [T_ROUTE] = "route", [T_ROUTES_BLOCK] = "block of routes", @@ -206,6 +209,8 @@ val_compare(const struct f_val *v1, const struct f_val *v2) return lcomm_cmp(v1->val.lc, v2->val.lc); case T_IP: return ipa_compare(v1->val.ip, v2->val.ip); + case T_MAC: + return mac_compare(v1->val.mac, v2->val.mac); case T_NET: return net_compare(v1->val.net, v2->val.net); case T_STRING: @@ -633,6 +638,7 @@ val_format(const struct f_val *v, buffer *buf) case T_EC: ec_format(buf2, v->val.ec); buffer_print(buf, "%s", buf2); return; case T_LC: lc_format(buf2, v->val.lc); buffer_print(buf, "%s", buf2); return; case T_RD: rd_format(v->val.ec, buf2, 1024); buffer_print(buf, "%s", buf2); return; + case T_MAC: buffer_print(buf, "%6b", v->val.mac.addr); return; case T_PREFIX_SET: trie_format(v->val.ti, buf); return; case T_SET: tree_format(v->val.t, buf); return; case T_ENUM: buffer_print(buf, "(enum %x)%u", v->type, v->val.i); return; diff --git a/filter/data.h b/filter/data.h index 21a78bf6..c97fecd5 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_NET_EVPN = 0x3a, /* new enums go here */ T_ENUM_EMPTY = 0x3f, /* Special hack for atomic_aggr */ @@ -63,6 +64,7 @@ enum f_type { T_RD = 0x2a, /* Route distinguisher for VPN addresses */ T_PATH_MASK_ITEM = 0x2b, /* Path mask item for path mask constructors */ T_BYTESTRING = 0x2c, + T_MAC = 0x2d, T_ROUTE = 0x78, T_ROUTES_BLOCK = 0x79, @@ -86,6 +88,7 @@ struct f_val { u64 ec; lcomm lc; ip_addr ip; + mac_addr mac; const net_addr *net; const char *s; const struct adata *bs; diff --git a/filter/f-inst.c b/filter/f-inst.c index 9cc46aa0..3a217507 100644 --- a/filter/f-inst.c +++ b/filter/f-inst.c @@ -1046,15 +1046,116 @@ ]]); /* Convert prefix to IP */ - METHOD_R(T_NET, ip, T_IP, ip, net_prefix(v1.val.net)); + METHOD(T_NET, ip, 0, [[ + const net_addr_union *net = (void *) v1.val.net; + + if ((net->n.type == NET_EVPN) && (net->evpn.subtype == NET_EVPN_MAC)) + RESULT(T_IP, ip, (net->n.length == sizeof(net_addr_evpn_mac_ip)) ? net->evpn.mac_ip.ip : IPA_NONE); + else + RESULT(T_IP, ip, net_prefix(v1.val.net)); + ]]); + + /* Get route distinguisher */ + METHOD(T_NET, rd, 0, [[ + if (!net_type_match(v1.val.net, NB_VPN | NB_EVPN)) + runtime( "VPN or EVPN address expected" ); - INST(FI_ROUTE_DISTINGUISHER, 1, 1) { - ARG(1, T_NET); - METHOD_CONSTRUCTOR("rd"); - if (!net_is_vpn(v1.val.net)) - runtime( "VPN address expected" ); RESULT(T_RD, ec, net_rd(v1.val.net)); - } + ]]); + + /* Get MAC address */ + METHOD(T_NET, mac, 0, [[ + const net_addr_union *net = (void *) v1.val.net; + + if (net->n.type == NET_ETH) + RESULT(T_MAC, mac, net->eth.mac); + else if ((net->n.type == NET_EVPN) && (net->evpn.subtype == NET_EVPN_MAC)) + RESULT(T_MAC, mac, net->evpn.mac.mac); + else + runtime( "Ethernet or EVPN MAC expected" ); + ]]); + + /* Get VLAN ID */ + METHOD(T_NET, vlan_id, 0, [[ + if (v1.val.net->type != NET_ETH) + runtime( "Ethernet address expected" ); + + const net_addr_eth *eth = (void *) v1.val.net; + RESULT(T_INT, i, eth->vid); + ]]); + + /* Get EVPN type */ + METHOD(T_NET, evpn_type, 0, [[ + if (v1.val.net->type != NET_EVPN) + runtime( "EVPN address expected" ); + + const net_addr_evpn *evpn = (void *) v1.val.net; + RESULT(T_ENUM_NET_EVPN, i, evpn->subtype); + ]]); + + /* Get EVPN tag */ + METHOD(T_NET, evpn_tag, 0, [[ + if (v1.val.net->type != NET_EVPN) + runtime( "EVPN address expected" ); + + const net_addr_evpn *evpn = (void *) v1.val.net; + if (evpn->subtype > NET_EVPN_IMET) + runtime( "EVPN EAD/MAC/IMET address expected" ); + + RESULT(T_INT, i, evpn->tag); + ]]); + + /* Get EVPN ESI */ + METHOD(T_NET, evpn_esi, 0, [[ + if (v1.val.net->type != NET_EVPN) + runtime( "EVPN address expected" ); + + const net_addr_evpn *evpn = (void *) v1.val.net; + const evpn_esi *esi; + + switch (evpn->subtype) + { + case NET_EVPN_EAD: + esi = &evpn->ead.esi; + break; + + case NET_EVPN_ES: + esi = &evpn->es.esi; + break; + + default: + runtime( "EVPN EAD/ES address expected" ); + } + + struct adata *bs; + bs = falloc(sizeof(struct adata) + sizeof(evpn_esi)); + bs->length = sizeof(evpn_esi); + memcpy(bs->data, esi, sizeof(evpn_esi)); + + RESULT(T_BYTESTRING, bs, bs); + ]]); + + /* Get EVPN outer IP */ + METHOD(T_NET, router_ip, 0, [[ + if (v1.val.net->type != NET_EVPN) + runtime( "EVPN address expected" ); + + const net_addr_evpn *evpn = (void *) v1.val.net; + + switch (evpn->subtype) + { + case NET_EVPN_IMET: + RESULT(T_IP, ip, evpn->imet.rtr); + break; + + case NET_EVPN_ES: + RESULT(T_IP, ip, evpn->es.rtr); + break; + + default: + runtime( "EVPN IMET/ES address expected" ); + } + ]]); /* Get first ASN from AS PATH */ METHOD_R(T_PATH, first, T_INT, i, ({ u32 as = 0; as_path_get_first(v1.val.ad, &as); as; })); diff --git a/filter/test.conf b/filter/test.conf index 02857eac..f8a68fa7 100644 --- a/filter/test.conf +++ b/filter/test.conf @@ -1830,6 +1830,107 @@ bt_test_suite(t_net_sadr, "Testing IPv6 SADR nets"); +/* + * Testing Ethernet nets + * --------------------- + */ + +function t_net_eth() +{ + prefix p; + + p = eth 12:3f:c9:48:9c:9b; + bt_assert(format(p) = "12:3f:c9:48:9c:9b"); + bt_assert(p.type = NET_ETH); + bt_assert(p.mac = 12:3f:c9:48:9c:9b); + bt_assert(p.vlan_id = 0); + + p = eth 16:36:f8:80:5b:48 vlan 100; + bt_assert(format(p) = "16:36:f8:80:5b:48 vlan 100"); + bt_assert(p.type = NET_ETH); + bt_assert(p.mac = 16:36:f8:80:5b:48); + bt_assert(p.vlan_id = 100); +} + +bt_test_suite(t_net_eth, "Testing Ethernet nets"); + + + + +/* + * Testing EVPN nets + * ----------------- + */ + +function t_net_evpn() +{ + prefix p; + + p = evpn ead 10:1010 210 00:10:20:30:40:50:60:70:80:90; + bt_assert(format(p) = "evpn ead 10:1010 210 00:10:20:30:40:50:60:70:80:90"); + bt_assert(p.type = NET_EVPN); + bt_assert(p.evpn_type = NET_EVPN_EAD); + bt_assert(p.rd = 10:1010); + bt_assert(p.evpn_tag = 210); + bt_assert(p.evpn_esi = 00:10:20:30:40:50:60:70:80:90); + + p = evpn mac 10:1020 220 12:a6:54:da:bc:bf *; + bt_assert(format(p) = "evpn mac 10:1020 220 12:a6:54:da:bc:bf *"); + bt_assert(p.type = NET_EVPN); + bt_assert(p.evpn_type = NET_EVPN_MAC); + bt_assert(p.rd = 10:1020); + bt_assert(p.evpn_tag = 220); + bt_assert(p.mac = 12:a6:54:da:bc:bf); + bt_assert(p.ip = ::); + + p = evpn mac 10:1020 224 16:c2:8d:50:86:c5 192.0.2.10; + bt_assert(format(p) = "evpn mac 10:1020 224 16:c2:8d:50:86:c5 192.0.2.10"); + bt_assert(p.type = NET_EVPN); + bt_assert(p.evpn_type = NET_EVPN_MAC); + bt_assert(p.rd = 10:1020); + bt_assert(p.evpn_tag = 224); + bt_assert(p.mac = 16:c2:8d:50:86:c5); + bt_assert(p.ip = 192.0.2.10); + + p = evpn mac 10:1020 226 2a:3b:24:3d:f0:a0 2001:db8:10:20::1; + bt_assert(format(p) = "evpn mac 10:1020 226 2a:3b:24:3d:f0:a0 2001:db8:10:20::1"); + bt_assert(p.type = NET_EVPN); + bt_assert(p.evpn_type = NET_EVPN_MAC); + bt_assert(p.rd = 10:1020); + bt_assert(p.evpn_tag = 226); + bt_assert(p.mac = 2a:3b:24:3d:f0:a0); + bt_assert(p.ip = 2001:db8:10:20::1); + + p = evpn imet 10:1030 234 192.0.2.20; + bt_assert(format(p) = "evpn imet 10:1030 234 192.0.2.20"); + bt_assert(p.type = NET_EVPN); + bt_assert(p.evpn_type = NET_EVPN_IMET); + bt_assert(p.rd = 10:1030); + bt_assert(p.evpn_tag = 234); + bt_assert(p.router_ip = 192.0.2.20); + + p = evpn imet 10:1030 236 2001:db8:10:20::2; + bt_assert(format(p) = "evpn imet 10:1030 236 2001:db8:10:20::2"); + bt_assert(p.type = NET_EVPN); + bt_assert(p.evpn_type = NET_EVPN_IMET); + bt_assert(p.rd = 10:1030); + bt_assert(p.evpn_tag = 236); + bt_assert(p.router_ip = 2001:db8:10:20::2); + + p = evpn es 10:1040 00:10:20:30:40:50:60:70:80:90 192.0.2.40; + bt_assert(format(p) = "evpn es 10:1040 00:10:20:30:40:50:60:70:80:90 192.0.2.40"); + bt_assert(p.type = NET_EVPN); + bt_assert(p.evpn_type = NET_EVPN_ES); + bt_assert(p.rd = 10:1040); + bt_assert(p.evpn_esi = 00:10:20:30:40:50:60:70:80:90); + bt_assert(p.router_ip = 192.0.2.40); +} + +bt_test_suite(t_net_evpn, "Testing EVPN nets"); + + + + /* * Testing defined() function * -------------------------- diff --git a/proto/aggregator/aggregator.c b/proto/aggregator/aggregator.c index e5c2a176..73836911 100644 --- a/proto/aggregator/aggregator.c +++ b/proto/aggregator/aggregator.c @@ -540,6 +540,9 @@ aggregator_rt_notify(struct proto *P, struct channel *src_ch, net *net, rte *new case T_IP: MX(ip); break; + case T_MAC: + MX(mac); + break; case T_NET: mem_hash_mix_num(&haux, net_hash(IT(net))); break;