Hoi folks,
I've started to toy with VPP and eVPN/VxLAN, and took a look at the evpn
branch from a few years ago.
For my network, I'll need the OSPFv3 'unnumbered' features we built, so
I thought I'd ask - would it be possible to rebase the evpn branch ?
I've taken a stab at it (see attached patch) by replaying the 9 commits
on top if HEAD (f1a7229d-evpn.diff).
It may not be correct, but it does compile and seemingly work :)
root@vpp0-0:/etc/bird# birdc show proto
BIRD 2.18+branch.evpn.1efd115564ea ready.
Name Proto Table State Since Info
device1 Device --- up 12:20:01.347
direct1 Direct --- up 12:20:01.347
kernel4 Kernel master4 up 12:20:01.347
kernel6 Kernel master6 up 12:20:01.347
static4 Static master4 up 12:20:01.347
static6 Static master6 up 12:20:01.347
bfd1 BFD --- up 12:20:01.347
ospf4 OSPF master4 up 12:20:01.347 Running
ospf6 OSPF master6 up 12:20:01.347 Running
bridge1 Bridge etab up 12:45:13.450
evpn1 EVPN --- up 12:45:13.450
I have a few further changes, notably a new 'vpp protocol' to handle
communication with the VPP dataplane for eVPN/VxLAN (and possibly later,
GENEVE and MPLS).
Before I get too far down the rabbit hole though, I'm also wondering: is
releasing eVPN in the cards?
groet,
Pim
--
Pim van Pelt <[email protected]>
PBVP1-RIPE https://ipng.ch/
diff --git a/conf/cf-lex.l b/conf/cf-lex.l
index 0bf76e9b..edd233fa 100644
--- a/conf/cf-lex.l
+++ b/conf/cf-lex.l
@@ -246,7 +246,19 @@ WHITE [ \t]
return IP4;
}
-({XIGIT}{2}){16,}|{XIGIT}{2}(:{XIGIT}{2}){15,}|hex:({XIGIT}{2}*|{XIGIT}{2}(:{XIGIT}{2})*)
{
+{XIGIT}{2}(:{XIGIT}{2}){5} {
+ mac_addr mac;
+ char *s = yytext;
+
+ int len = bstrhextobin(s, mac.addr);
+ if (len != 6)
+ cf_error("Invalid MAC address");
+
+ cf_lval.mac = mac;
+ return MAC_;
+}
+
+({XIGIT}{2}){10,}|{XIGIT}{2}(:{XIGIT}{2}){9,}|hex:({XIGIT}{2}*|{XIGIT}{2}(:{XIGIT}{2})*)
{
char *s = yytext;
struct adata *bs;
diff --git a/conf/confbase.Y b/conf/confbase.Y
index 27c422ea..9e566ce7 100644
--- a/conf/confbase.Y
+++ b/conf/confbase.Y
@@ -16,6 +16,7 @@ CF_HDR
#include "lib/socket.h"
#include "lib/timer.h"
#include "lib/string.h"
+#include "lib/evpn.h"
#include "nest/protocol.h"
#include "nest/iface.h"
#include "nest/route.h"
@@ -86,7 +87,9 @@ CF_DECLS
ip_addr a;
ip4_addr ip4;
ip6_addr ip6;
+ mac_addr mac;
net_addr net;
+ evpn_esi esi;
net_addr *net_ptr;
struct symbol *s;
struct keyword *kw;
@@ -135,6 +138,7 @@ CF_DECLS
%token <i> NUM ENUM_TOKEN
%token <ip4> IP4
%token <ip6> IP6
+%token <mac> MAC_
%token <rd> VPN_RD
%token <s> CF_SYM_KNOWN CF_SYM_UNDEFINED CF_SYM_METHOD_BARE CF_SYM_METHOD_ARGS
%token <t> TEXT
@@ -146,8 +150,10 @@ CF_DECLS
%type <time> expr_us time
%type <a> ipa
%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_ net_aspa_
+%type <net_ptr> net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_
net_roa_ net_ip6_sadr_ net_eth_ net_mpls_ net_aspa_
+%type <net_ptr> net_evpn_ net_evpn_ead_ net_evpn_mac_ net_evpn_mac_ip_
net_evpn_imet_ net_evpn_es_
%type <mls> label_stack_start label_stack
+%type <esi> evpn_esi
%type <t> text opttext
%type <bs> bytestring
@@ -168,7 +174,8 @@ CF_DECLS
/* See r_args */
%expect 2
-CF_KEYWORDS(DEFINE, ON, OFF, YES, NO, S, MS, US, PORT, VPN, MPLS, FROM, MAX,
AS)
+CF_KEYWORDS(DEFINE, ON, OFF, YES, NO, S, MS, US, PORT, VPN, ETH, VLAN, MPLS,
FROM, MAX, AS)
+CF_KEYWORDS(EVPN, EAD, MAC, IMET, ES)
CF_GRAMMAR
@@ -360,6 +367,18 @@ net_roa6_: net_ip6_ MAX expr AS expr
cf_error("Invalid max prefix length %u", $3);
};
+net_eth_: ETH MAC_
+{
+ $$ = cfg_alloc(sizeof(net_addr_eth));
+ net_fill_eth($$, $2, 0);
+}
+
+net_eth_: ETH MAC_ VLAN NUM
+{
+ $$ = cfg_alloc(sizeof(net_addr_eth));
+ net_fill_eth($$, $2, $4);
+}
+
net_mpls_: MPLS expr
{
$$ = cfg_alloc(sizeof(net_addr_mpls));
@@ -372,6 +391,46 @@ net_aspa_: ASPA expr
net_fill_aspa($$, $2);
}
+evpn_esi: bytestring
+{
+ if ($1->length != 10)
+ cf_error("Invalid ESI length %u", (uint) $1->length);
+
+ memcpy(&$$, $1->data, 10);
+}
+
+net_evpn_ead_: EVPN EAD VPN_RD NUM evpn_esi
+{
+ $$ = cfg_alloc(sizeof(net_addr_evpn_ead));
+ net_fill_evpn_ead($$, rd_to_u64($3), $4, $5);
+}
+
+net_evpn_mac_: EVPN MAC VPN_RD NUM MAC_ '*'
+{
+ $$ = cfg_alloc(sizeof(net_addr_evpn_mac));
+ net_fill_evpn_mac($$, rd_to_u64($3), $4, $5);
+}
+
+net_evpn_mac_ip_: EVPN MAC VPN_RD NUM MAC_ ipa
+{
+ $$ = cfg_alloc(sizeof(net_addr_evpn_mac_ip));
+ net_fill_evpn_mac_ip($$, rd_to_u64($3), $4, $5, $6);
+}
+
+net_evpn_imet_: EVPN IMET VPN_RD NUM ipa
+{
+ $$ = cfg_alloc(sizeof(net_addr_evpn_imet));
+ net_fill_evpn_imet($$, rd_to_u64($3), $4, $5);
+}
+
+net_evpn_es_: EVPN ES VPN_RD evpn_esi ipa
+{
+ $$ = cfg_alloc(sizeof(net_addr_evpn_es));
+ net_fill_evpn_es($$, rd_to_u64($3), $4, $5);
+}
+
+net_evpn_: net_evpn_ead_ | net_evpn_mac_ | net_evpn_mac_ip_ | net_evpn_imet_ |
net_evpn_es_ ;
+
net_ip_: net_ip4_ | net_ip6_ ;
net_vpn_: net_vpn4_ | net_vpn6_ ;
net_roa_: net_roa4_ | net_roa6_ ;
@@ -382,8 +441,10 @@ net_:
| net_roa_
| net_flow_
| net_ip6_sadr_
+ | net_eth_
| net_mpls_
| net_aspa_
+ | net_evpn_
;
diff --git a/configure.ac b/configure.ac
index c27573cf..38908bae 100644
--- a/configure.ac
+++ b/configure.ac
@@ -319,7 +319,7 @@ if test "$enable_mpls_kernel" != no ; then
fi
fi
-all_protocols="aggregator $proto_bfd babel bgp l3vpn mrt ospf perf pipe radv
rip rpki static"
+all_protocols="aggregator $proto_bfd babel bgp bridge evpn l3vpn mrt ospf perf
pipe radv rip rpki static"
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
@@ -332,6 +332,8 @@ AH_TEMPLATE([CONFIG_BABEL], [Babel protocol])
AH_TEMPLATE([CONFIG_BFD], [BFD protocol])
AH_TEMPLATE([CONFIG_BGP], [BGP protocol])
AH_TEMPLATE([CONFIG_BMP], [BMP protocol])
+AH_TEMPLATE([CONFIG_BRIDGE], [Bridge protocol])
+AH_TEMPLATE([CONFIG_EVPN], [EVPN protocol])
AH_TEMPLATE([CONFIG_L3VPN], [L3VPN protocol])
AH_TEMPLATE([CONFIG_MRT], [MRT protocol])
AH_TEMPLATE([CONFIG_OSPF], [OSPF protocol])
diff --git a/filter/config.Y b/filter/config.Y
index 1fa17afd..064d36bd 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, ENUM,
+ INT, BOOL, IP, PREFIX, RD, MAC, PAIR, QUAD, EC, LC, ENUM,
SET, STRING, BYTESTRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST,
IF, THEN, ELSE, CASE,
FOR, DO,
@@ -456,6 +457,7 @@ type:
| BOOL { $$ = T_BOOL; }
| IP { $$ = T_IP; }
| RD { $$ = T_RD; }
+ | MAC { $$ = T_MAC; }
| PREFIX { $$ = T_NET; }
| PAIR { $$ = T_PAIR; }
| QUAD { $$ = T_QUAD; }
@@ -477,8 +479,9 @@ type:
case T_ENUM:
case T_EC:
case T_LC:
- case T_RD:
case T_IP:
+ case T_RD:
+ case T_MAC:
$$ = T_SET;
break;
@@ -648,6 +651,7 @@ set_atom0:
NUM { $$.type = T_INT; $$.val.i = $1; }
| fipa { $$ = $1; }
| VPN_RD { $$.type = T_RD; $$.val.rd = $1; }
+ | MAC_ { $$.type = T_MAC; $$.val.mac = $1; }
| ENUM_TOKEN { $$.type = pair_a($1); $$.val.i = pair_b($1); }
| '(' term ')' {
$$ = cf_eval($2, T_VOID);
@@ -808,6 +812,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.rd = $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 211faeea..4f9760e5 100644
--- a/filter/data.c
+++ b/filter/data.c
@@ -44,6 +44,7 @@ static const char * const f_type_str[] = {
[T_ENUM_RA_PREFERENCE] = "enum ra_preference",
[T_ENUM_AF] = "enum af",
[T_ENUM_MPLS_POLICY] = "enum mpls_policy",
+ [T_ENUM_EVPN_TYPE] = "enum net_evpn",
[T_IP] = "ip",
[T_NET] = "prefix",
@@ -57,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",
@@ -207,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:;
@@ -635,6 +639,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.rd, 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 a1986496..a2e29252 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_EVPN_TYPE = 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,
@@ -87,6 +89,7 @@ struct f_val {
lcomm lc;
vpn_rd rd;
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 81360430..4350d8dc 100644
--- a/filter/f-inst.c
+++ b/filter/f-inst.c
@@ -1095,16 +1095,118 @@
]]);
/* 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 */
INST(FI_ROUTE_DISTINGUISHER, 1, 1) {
ARG(1, T_NET);
METHOD_CONSTRUCTOR("rd");
- if (!net_is_vpn(v1.val.net))
- runtime( "VPN address expected" );
+ if (!net_type_match(v1.val.net, NB_VPN | NB_EVPN))
+ runtime( "VPN or EVPN address expected" );
RESULT(T_RD, rd, 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_EVPN_TYPE, 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 096798c5..1023f2b0 100644
--- a/filter/test.conf
+++ b/filter/test.conf
@@ -2031,6 +2031,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/lib/Makefile b/lib/Makefile
index 812f721c..21aad378 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,4 +1,4 @@
-src := bitmap.c bitops.c blake2s.c blake2b.c checksum.c event.c flowspec.c
idm.c ip.c lists.c mac.c md5.c mempool.c net.c patmatch.c printf.c resource.c
sha1.c sha256.c sha512.c slab.c slists.c strtoul.c tbf.c timer.c xmalloc.c
+src := bitmap.c bitops.c blake2s.c blake2b.c checksum.c event.c evpn.c
flowspec.c idm.c ip.c lists.c mac.c md5.c mempool.c net.c patmatch.c printf.c
resource.c sha1.c sha256.c sha512.c slab.c slists.c strtoul.c tbf.c timer.c
xmalloc.c
obj := $(src-o-files)
$(all-daemon)
diff --git a/lib/evpn.c b/lib/evpn.c
new file mode 100644
index 00000000..997c1f7e
--- /dev/null
+++ b/lib/evpn.c
@@ -0,0 +1,38 @@
+/*
+ * BIRD Internet Routing Daemon -- EVPN Net Type
+ *
+ * (c) 2023 Ondrej Zajicek <[email protected]>
+ * (c) 2023 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include "nest/bird.h"
+#include "lib/net.h"
+
+uint
+evpn_format(char *buf, uint blen, const net_addr_evpn *n)
+{
+ char rds[32];
+ rd_format(rd_from_u64(n->rd), rds, 32);
+
+ switch (n->subtype)
+ {
+ case NET_EVPN_EAD:
+ return bsnprintf(buf, blen, "evpn ead %s %u %10b", rds, n->tag,
&n->ead.esi);
+
+ case NET_EVPN_MAC:
+ if (n->length < sizeof(net_addr_evpn_mac_ip))
+ return bsnprintf(buf, blen, "evpn mac %s %u %6b *", rds, n->tag,
&n->mac.mac);
+ else
+ return bsnprintf(buf, blen, "evpn mac %s %u %6b %I", rds, n->tag,
&n->mac_ip.mac, n->mac_ip.ip);
+
+ case NET_EVPN_IMET:
+ return bsnprintf(buf, blen, "evpn imet %s %u %I", rds, n->tag,
n->imet.rtr);
+
+ case NET_EVPN_ES:
+ return bsnprintf(buf, blen, "evpn es %s %10b %I", rds, &n->es.esi,
n->es.rtr);
+ }
+
+ bug("unknown EVPN type %d", n->subtype);
+}
diff --git a/lib/evpn.h b/lib/evpn.h
new file mode 100644
index 00000000..c340c385
--- /dev/null
+++ b/lib/evpn.h
@@ -0,0 +1,50 @@
+/*
+ * BIRD Internet Routing Daemon -- EVPN Net Type
+ *
+ * (c) 2023 Ondrej Zajicek <[email protected]>
+ * (c) 2023 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_EVPN_NET_H_
+#define _BIRD_EVPN_NET_H_
+
+enum evpn_net_type {
+ NET_EVPN_EAD = 1,
+ NET_EVPN_MAC = 2,
+ NET_EVPN_IMET = 3,
+ NET_EVPN_ES = 4,
+ NET_EVPN_MAX
+};
+
+enum evpn_esi_type {
+ EVPN_ESI_MANUAL = 0,
+ EVPN_ESI_LACP = 1,
+ EVPN_ESI_MAX
+};
+
+typedef struct evpn_esi {
+ u8 type;
+ u8 value[9];
+} evpn_esi;
+
+typedef struct mac_addr {
+ u8 addr[6];
+} mac_addr;
+
+#define MAC_NONE ((mac_addr){ })
+
+static inline int mac_zero(mac_addr a)
+{ return !memcmp(&a, &MAC_NONE, sizeof(mac_addr)); }
+
+static inline int mac_nonzero(mac_addr a)
+{ return !mac_zero(a); }
+
+static inline int mac_compare(mac_addr a, mac_addr b)
+{ return memcmp(&a, &b, sizeof(mac_addr)); }
+
+union net_addr_evpn;
+uint evpn_format(char *buf, uint blen, const union net_addr_evpn *n);
+
+#endif
diff --git a/lib/net.c b/lib/net.c
index 64cf9e04..43e6cb15 100644
--- a/lib/net.c
+++ b/lib/net.c
@@ -15,8 +15,10 @@ const char * const net_label[] = {
[NET_FLOW4] = "flow4",
[NET_FLOW6] = "flow6",
[NET_IP6_SADR]= "ipv6-sadr",
+ [NET_ETH] = "eth",
[NET_MPLS] = "mpls",
[NET_ASPA] = "aspa",
+ [NET_EVPN] = "evpn",
};
const u16 net_addr_length[] = {
@@ -29,8 +31,10 @@ const u16 net_addr_length[] = {
[NET_FLOW4] = 0,
[NET_FLOW6] = 0,
[NET_IP6_SADR]= sizeof(net_addr_ip6_sadr),
+ [NET_ETH] = sizeof(net_addr_eth),
[NET_MPLS] = sizeof(net_addr_mpls),
[NET_ASPA] = sizeof(net_addr_aspa),
+ [NET_EVPN] = 0,
};
const u8 net_max_prefix_length[] = {
@@ -43,8 +47,10 @@ const u8 net_max_prefix_length[] = {
[NET_FLOW4] = IP4_MAX_PREFIX_LENGTH,
[NET_FLOW6] = IP6_MAX_PREFIX_LENGTH,
[NET_IP6_SADR]= IP6_MAX_PREFIX_LENGTH,
+ [NET_ETH] = 0,
[NET_MPLS] = 0,
[NET_ASPA] = 0,
+ [NET_EVPN] = 0,
};
const u16 net_max_text_length[] = {
@@ -57,8 +63,10 @@ const u16 net_max_text_length[] = {
[NET_FLOW4] = 0, /* "flow4 { ... }" */
[NET_FLOW6] = 0, /* "flow6 { ... }" */
[NET_IP6_SADR]= 92, /* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128 from
ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" */
+ [NET_ETH] = 28, /* "11:22:33:44:55:66 vlan 65535" */
[NET_MPLS] = 7, /* "1048575" */
[NET_ASPA] = 10, /* "4294967295" */
+ [NET_EVPN] = 0,
};
/* There should be no implicit padding in net_addr structures */
@@ -72,6 +80,7 @@ STATIC_ASSERT(sizeof(net_addr_roa6) == 28);
STATIC_ASSERT(sizeof(net_addr_flow4) == 8);
STATIC_ASSERT(sizeof(net_addr_flow6) == 20);
STATIC_ASSERT(sizeof(net_addr_ip6_sadr) == 40);
+STATIC_ASSERT(sizeof(net_addr_eth) == 12);
STATIC_ASSERT(sizeof(net_addr_mpls) == 8);
STATIC_ASSERT(sizeof(net_addr_aspa) == 8);
@@ -90,6 +99,13 @@ STATIC_ASSERT(alignof(net_addr_ip6_sadr) ==
alignof(net_addr));
STATIC_ASSERT(alignof(net_addr_mpls) == alignof(net_addr));
STATIC_ASSERT(alignof(net_addr_aspa) == alignof(net_addr));
+STATIC_ASSERT(sizeof(net_addr_evpn_ead) == 32);
+STATIC_ASSERT(sizeof(net_addr_evpn_mac) == 24);
+STATIC_ASSERT(sizeof(net_addr_evpn_mac_ip) == 40);
+STATIC_ASSERT(sizeof(net_addr_evpn_imet) == 32);
+STATIC_ASSERT(sizeof(net_addr_evpn_es) == 48);
+STATIC_ASSERT(sizeof(net_addr_evpn) == 48);
+
int
rd_format(const vpn_rd rd_, char *buf, int buflen)
@@ -143,10 +159,22 @@ net_format(const net_addr *N, char *buf, int buflen)
return flow6_net_format(buf, buflen, &n->flow6);
case NET_IP6_SADR:
return bsnprintf(buf, buflen, "%I6/%d from %I6/%d",
n->ip6_sadr.dst_prefix, n->ip6_sadr.dst_pxlen, n->ip6_sadr.src_prefix,
n->ip6_sadr.src_pxlen);
+ case NET_ETH:
+ {
+ int c = bsnprintf(buf, buflen, "%6b", &n->eth.mac);
+ ADVANCE(buf, buflen, c);
+
+ if (n->eth.vid)
+ c += bsnprintf(buf, buflen, " vlan %d", (int) n->eth.vid);
+
+ return c;
+ }
case NET_MPLS:
return bsnprintf(buf, buflen, "%u", n->mpls.label);
case NET_ASPA:
return bsnprintf(buf, buflen, "%u", n->aspa.asn);
+ case NET_EVPN:
+ return evpn_format(buf, buflen, &n->evpn);;
}
bug("unknown network type");
@@ -170,8 +198,10 @@ net_pxmask(const net_addr *a)
case NET_IP6_SADR:
return ipa_from_ip6(ip6_mkmask(net6_pxlen(a)));
+ case NET_ETH:
case NET_MPLS:
case NET_ASPA:
+ case NET_EVPN:
default:
return IPA_NONE;
}
@@ -203,10 +233,14 @@ net_compare(const net_addr *a, const net_addr *b)
return net_compare_flow6((const net_addr_flow6 *) a, (const net_addr_flow6
*) b);
case NET_IP6_SADR:
return net_compare_ip6_sadr((const net_addr_ip6_sadr *) a, (const
net_addr_ip6_sadr *) b);
+ case NET_ETH:
+ return net_compare_eth((const net_addr_eth *) a, (const net_addr_eth *) b);
case NET_MPLS:
return net_compare_mpls((const net_addr_mpls *) a, (const net_addr_mpls *)
b);
case NET_ASPA:
return net_compare_aspa((const net_addr_aspa *) a, (const net_addr_aspa *)
b);
+ case NET_EVPN:
+ return net_compare_evpn((const net_addr_evpn *) a, (const net_addr_evpn *)
b);
}
return 0;
}
@@ -227,8 +261,10 @@ net_hash(const net_addr *n)
case NET_FLOW4: return NET_HASH(n, flow4);
case NET_FLOW6: return NET_HASH(n, flow6);
case NET_IP6_SADR: return NET_HASH(n, ip6_sadr);
+ case NET_ETH: return NET_HASH(n, eth);
case NET_MPLS: return NET_HASH(n, mpls);
case NET_ASPA: return NET_HASH(n, aspa);
+ case NET_EVPN: return NET_HASH(n, evpn);
default: bug("invalid type");
}
}
@@ -250,8 +286,10 @@ net_validate(const net_addr *n)
case NET_FLOW4: return NET_VALIDATE(n, flow4);
case NET_FLOW6: return NET_VALIDATE(n, flow6);
case NET_IP6_SADR: return NET_VALIDATE(n, ip6_sadr);
+ case NET_ETH: return NET_VALIDATE(n, eth);
case NET_MPLS: return NET_VALIDATE(n, mpls);
case NET_ASPA: return NET_VALIDATE(n, aspa);
+ case NET_EVPN: return NET_VALIDATE(n, evpn);
default: return 0;
}
}
@@ -278,8 +316,10 @@ net_normalize(net_addr *N)
case NET_IP6_SADR:
return net_normalize_ip6_sadr(&n->ip6_sadr);
+ case NET_ETH:
case NET_MPLS:
case NET_ASPA:
+ case NET_EVPN:
return;
}
}
@@ -306,8 +346,10 @@ net_classify(const net_addr *N)
case NET_IP6_SADR:
return ip6_zero(n->ip6_sadr.dst_prefix) ? (IADDR_HOST | SCOPE_UNIVERSE) :
ip6_classify(&n->ip6_sadr.dst_prefix);
+ case NET_ETH:
case NET_MPLS:
case NET_ASPA:
+ case NET_EVPN: /* ?? */
return IADDR_HOST | SCOPE_UNIVERSE;
}
@@ -340,8 +382,10 @@ ipa_in_netX(const ip_addr a, const net_addr *n)
return ip6_zero(ip6_and(ip6_xor(ipa_to_ip6(a), net6_prefix(n)),
ip6_mkmask(net6_pxlen(n))));
+ case NET_ETH:
case NET_MPLS:
case NET_ASPA:
+ case NET_EVPN:
default:
return 0;
}
diff --git a/lib/net.h b/lib/net.h
index a1fd0291..8d6f58bf 100644
--- a/lib/net.h
+++ b/lib/net.h
@@ -11,6 +11,8 @@
#define _BIRD_NET_H_
#include "lib/ip.h"
+#include "lib/evpn.h"
+#include "lib/hash.h"
#define NET_IP4 1
@@ -22,9 +24,11 @@
#define NET_FLOW4 7
#define NET_FLOW6 8
#define NET_IP6_SADR 9
-#define NET_MPLS 10
-#define NET_ASPA 11
-#define NET_MAX 12
+#define NET_ETH 10
+#define NET_MPLS 11
+#define NET_ASPA 12
+#define NET_EVPN 13
+#define NET_MAX 14
#define NB_IP4 (1 << NET_IP4)
#define NB_IP6 (1 << NET_IP6)
@@ -35,14 +39,16 @@
#define NB_FLOW4 (1 << NET_FLOW4)
#define NB_FLOW6 (1 << NET_FLOW6)
#define NB_IP6_SADR (1 << NET_IP6_SADR)
+#define NB_ETH (1 << NET_ETH)
#define NB_MPLS (1 << NET_MPLS)
#define NB_ASPA (1 << NET_ASPA)
+#define NB_EVPN (1 << NET_EVPN)
#define NB_IP (NB_IP4 | NB_IP6)
#define NB_VPN (NB_VPN4 | NB_VPN6)
#define NB_ROA (NB_ROA4 | NB_ROA6)
#define NB_FLOW (NB_FLOW4 | NB_FLOW6)
-#define NB_DEST (NB_IP | NB_IP6_SADR | NB_VPN | NB_MPLS)
+#define NB_DEST (NB_IP | NB_IP6_SADR | NB_VPN | NB_ETH |
NB_MPLS | NB_EVPN)
#define NB_ANY 0xffffffff
@@ -119,6 +125,14 @@ typedef struct net_addr_flow6 {
byte data[0];
} net_addr_flow6;
+typedef struct net_addr_eth {
+ u8 type;
+ u8 pxlen;
+ u16 length;
+ mac_addr mac;
+ u16 vid;
+} net_addr_eth;
+
typedef struct net_addr_mpls {
u8 type;
u8 pxlen;
@@ -142,6 +156,78 @@ typedef struct net_addr_ip6_sadr {
ip6_addr src_prefix;
} net_addr_ip6_sadr;
+typedef struct net_addr_evpn_ead {
+ u8 type;
+ u8 subtype;
+ u16 length;
+ u32 tag;
+ u64 rd;
+
+ evpn_esi esi;
+ u8 padding[6];
+} net_addr_evpn_ead;
+
+typedef struct net_addr_evpn_mac {
+ u8 type;
+ u8 subtype;
+ u16 length;
+ u32 tag;
+ u64 rd;
+
+ mac_addr mac;
+ u16 padding;
+} net_addr_evpn_mac;
+
+typedef struct net_addr_evpn_mac_ip {
+ u8 type;
+ u8 subtype;
+ u16 length;
+ u32 tag;
+ u64 rd;
+
+ mac_addr mac;
+ u16 padding0;
+ ip_addr ip;
+} net_addr_evpn_mac_ip;
+
+typedef struct net_addr_evpn_imet {
+ u8 type;
+ u8 subtype;
+ u16 length;
+ u32 tag;
+ u64 rd;
+
+ ip_addr rtr;
+} net_addr_evpn_imet;
+
+typedef struct net_addr_evpn_es {
+ u8 type;
+ u8 subtype;
+ u16 length;
+ u32 tag; /* unused */
+ u64 rd;
+
+ evpn_esi esi;
+ u8 padding[6];
+ ip_addr rtr;
+} net_addr_evpn_es;
+
+typedef union net_addr_evpn {
+ struct {
+ u8 type;
+ u8 subtype;
+ u16 length;
+ u32 tag;
+ u64 rd;
+ byte data[0];
+ };
+ net_addr_evpn_ead ead;
+ net_addr_evpn_mac mac;
+ net_addr_evpn_mac_ip mac_ip;
+ net_addr_evpn_imet imet;
+ net_addr_evpn_es es;
+} net_addr_evpn;
+
typedef union net_addr_union {
net_addr n;
net_addr_ip4 ip4;
@@ -153,8 +239,10 @@ typedef union net_addr_union {
net_addr_flow4 flow4;
net_addr_flow6 flow6;
net_addr_ip6_sadr ip6_sadr;
+ net_addr_eth eth;
net_addr_mpls mpls;
net_addr_aspa aspa;
+ net_addr_evpn evpn;
} net_addr_union;
@@ -193,6 +281,9 @@ extern const u16 net_max_text_length[];
#define NET_ADDR_IP6_SADR(dst_prefix,dst_pxlen,src_prefix,src_pxlen) \
((net_addr_ip6_sadr) { NET_IP6_SADR, dst_pxlen, sizeof(net_addr_ip6_sadr),
dst_prefix, src_pxlen, src_prefix })
+#define NET_ADDR_ETH(mac, vid) \
+ ((net_addr_eth) { NET_ETH, 48, sizeof(net_addr_eth), mac, vid })
+
#define NET_ADDR_ASPA(asn) \
((net_addr_aspa) { NET_ASPA, 32, sizeof(net_addr_aspa), asn })
@@ -200,6 +291,22 @@ extern const u16 net_max_text_length[];
((net_addr_mpls) { NET_MPLS, 20, sizeof(net_addr_mpls), label })
+#define NET_ADDR_EVPN_EAD(rd, tag, esi) \
+ ((net_addr_evpn_ead) { NET_EVPN, NET_EVPN_EAD, sizeof(net_addr_evpn_ead),
tag, rd, .esi = esi })
+
+#define NET_ADDR_EVPN_MAC(rd, tag, mac) \
+ ((net_addr_evpn_mac) { NET_EVPN, NET_EVPN_MAC, sizeof(net_addr_evpn_mac),
tag, rd, .mac = mac })
+
+#define NET_ADDR_EVPN_MAC_IP(rd, tag, mac, ip) \
+ ((net_addr_evpn_mac_ip) { NET_EVPN, NET_EVPN_MAC,
sizeof(net_addr_evpn_mac_ip), tag, rd, .mac = mac, .ip = ip })
+
+#define NET_ADDR_EVPN_IMET(rd, tag, rtr) \
+ ((net_addr_evpn_imet) { NET_EVPN, NET_EVPN_IMET, sizeof(net_addr_evpn_imet),
tag, rd, .rtr = rtr })
+
+#define NET_ADDR_EVPN_ES(rd, esi, rtr) \
+ ((net_addr_evpn_es) { NET_EVPN, NET_EVPN_ES, sizeof(net_addr_evpn_es), 0,
rd, .esi = esi, .rtr = rtr })
+
+
static inline void net_fill_ip4(net_addr *a, ip4_addr prefix, uint pxlen)
{ *(net_addr_ip4 *)a = NET_ADDR_IP4(prefix, pxlen); }
@@ -221,12 +328,30 @@ static inline void net_fill_roa6(net_addr *a, ip6_addr
prefix, uint pxlen, uint
static inline void net_fill_ip6_sadr(net_addr *a, ip6_addr dst_prefix, uint
dst_pxlen, ip6_addr src_prefix, uint src_pxlen)
{ *(net_addr_ip6_sadr *)a = NET_ADDR_IP6_SADR(dst_prefix, dst_pxlen,
src_prefix, src_pxlen); }
+static inline void net_fill_eth(net_addr *a, mac_addr mac, u16 vid)
+{ *(net_addr_eth *)a = NET_ADDR_ETH(mac, vid); }
+
static inline void net_fill_mpls(net_addr *a, u32 label)
{ *(net_addr_mpls *)a = NET_ADDR_MPLS(label); }
static inline void net_fill_aspa(net_addr *a, u32 asn)
{ *(net_addr_aspa *)a = NET_ADDR_ASPA(asn); }
+static inline void net_fill_evpn_ead(net_addr *a, u64 rd, u32 tag, evpn_esi
esi)
+{ *(net_addr_evpn_ead *)a = NET_ADDR_EVPN_EAD(rd, tag, esi); }
+
+static inline void net_fill_evpn_mac(net_addr *a, u64 rd, u32 tag, mac_addr
mac)
+{ *(net_addr_evpn_mac *)a = NET_ADDR_EVPN_MAC(rd, tag, mac); }
+
+static inline void net_fill_evpn_mac_ip(net_addr *a, u64 rd, u32 tag, mac_addr
mac, ip_addr ip)
+{ *(net_addr_evpn_mac_ip *)a = NET_ADDR_EVPN_MAC_IP(rd, tag, mac, ip); }
+
+static inline void net_fill_evpn_imet(net_addr *a, u64 rd, u32 tag, ip_addr
rtr)
+{ *(net_addr_evpn_imet *)a = NET_ADDR_EVPN_IMET(rd, tag, rtr); }
+
+static inline void net_fill_evpn_es(net_addr *a, u64 rd, evpn_esi esi, ip_addr
rtr)
+{ *(net_addr_evpn_es *)a = NET_ADDR_EVPN_ES(rd, esi, rtr); }
+
static inline void net_fill_ipa(net_addr *a, ip_addr prefix, uint pxlen)
{
if (ipa_is_ip4(prefix))
@@ -314,19 +439,25 @@ static inline ip_addr net_prefix(const net_addr *a)
case NET_IP6_SADR:
return ipa_from_ip6(net6_prefix(a));
+ case NET_ETH:
case NET_MPLS:
case NET_ASPA:
+ case NET_EVPN:
default:
return IPA_NONE;
}
}
-static inline u32 net_mpls(const net_addr *a)
+static inline mac_addr net_mac_addr(const net_addr *a)
{
- if (a->type == NET_MPLS)
- return ((net_addr_mpls *) a)->label;
+ ASSERT_DIE(a->type == NET_ETH);
+ return ((net_addr_eth *) a)->mac;
+}
- bug("Can't call net_mpls on non-mpls net_addr");
+static inline u32 net_mpls(const net_addr *a)
+{
+ ASSERT_DIE(a->type == NET_MPLS);
+ return ((net_addr_mpls *) a)->label;
}
static inline uint net4_pxlen(const net_addr *a)
@@ -348,6 +479,8 @@ static inline vpn_rd net_rd(const net_addr *a)
return ((net_addr_vpn4 *)a)->rd;
case NET_VPN6:
return ((net_addr_vpn6 *)a)->rd;
+ case NET_EVPN:
+ return rd_from_u64(((net_addr_evpn *)a)->rd);
}
return RD_NONE;
}
@@ -383,12 +516,18 @@ static inline int net_equal_flow6(const net_addr_flow6
*a, const net_addr_flow6
static inline int net_equal_ip6_sadr(const net_addr_ip6_sadr *a, const
net_addr_ip6_sadr *b)
{ return !memcmp(a, b, sizeof(net_addr_ip6_sadr)); }
+static inline int net_equal_eth(const net_addr_eth *a, const net_addr_eth *b)
+{ return !memcmp(a, b, sizeof(net_addr_eth)); }
+
static inline int net_equal_mpls(const net_addr_mpls *a, const net_addr_mpls
*b)
{ return !memcmp(a, b, sizeof(net_addr_mpls)); }
static inline int net_equal_aspa(const net_addr_aspa *a, const net_addr_aspa
*b)
{ return !memcmp(a, b, sizeof(net_addr_aspa)); }
+static inline int net_equal_evpn(const net_addr_evpn *a, const net_addr_evpn
*b)
+{ return net_equal((const net_addr *) a, (const net_addr *) b); }
+
static inline int net_equal_prefix_roa4(const net_addr_roa4 *a, const
net_addr_roa4 *b)
{ return ip4_equal(a->prefix, b->prefix) && (a->pxlen == b->pxlen); }
@@ -427,6 +566,9 @@ static inline int net_zero_flow4(const net_addr_flow4 *a)
static inline int net_zero_flow6(const net_addr_flow6 *a)
{ return !a->pxlen && ip6_zero(a->prefix) && (a->length ==
sizeof(net_addr_flow6)); }
+static inline int net_zero_eth(const net_addr_eth *a)
+{ return mac_zero(a->mac) && !a->vid; }
+
static inline int net_zero_mpls(const net_addr_mpls *a)
{ return !a->label; }
@@ -465,12 +607,22 @@ static inline int net_compare_ip6_sadr(const
net_addr_ip6_sadr *a, const net_add
ip6_compare(a->src_prefix, b->src_prefix) ?: uint_cmp(a->src_pxlen,
b->src_pxlen);
}
+static inline int net_compare_eth(const net_addr_eth *a, const net_addr_eth *b)
+{ return uint_cmp(a->vid, b->vid) ?: mac_compare(a->mac, b->mac); }
+
static inline int net_compare_mpls(const net_addr_mpls *a, const net_addr_mpls
*b)
{ return uint_cmp(a->label, b->label); }
static inline int net_compare_aspa(const net_addr_aspa *a, const net_addr_aspa
*b)
{ return uint_cmp(a->asn, b->asn); }
+static inline int net_compare_evpn(const net_addr_evpn *a, const net_addr_evpn
*b)
+{
+ return
+ uint_cmp(a->subtype, b->subtype) ?: u64_cmp(a->rd, b->rd) ?:
uint_cmp(a->tag, b->tag) ?:
+ uint_cmp(a->length, b->length) ?: memcmp(a->data, b->data, a->length -
OFFSETOF(net_addr_evpn, data));
+}
+
int net_compare(const net_addr *a, const net_addr *b);
@@ -504,12 +656,18 @@ static inline void net_copy_flow6(net_addr_flow6 *dst,
const net_addr_flow6 *src
static inline void net_copy_ip6_sadr(net_addr_ip6_sadr *dst, const
net_addr_ip6_sadr *src)
{ memcpy(dst, src, sizeof(net_addr_ip6_sadr)); }
+static inline void net_copy_eth(net_addr_eth *dst, const net_addr_eth *src)
+{ memcpy(dst, src, sizeof(net_addr_eth)); }
+
static inline void net_copy_mpls(net_addr_mpls *dst, const net_addr_mpls *src)
{ memcpy(dst, src, sizeof(net_addr_mpls)); }
static inline void net_copy_aspa(net_addr_aspa *dst, const net_addr_aspa *src)
{ memcpy(dst, src, sizeof(net_addr_aspa)); }
+static inline void net_copy_evpn(net_addr_evpn *dst, const net_addr_evpn *src)
+{ memcpy(dst, src, src->length); }
+
static inline u32 px4_hash(ip4_addr prefix, u32 pxlen)
{ return ip4_hash(prefix) ^ (pxlen << 26); }
@@ -550,12 +708,18 @@ static inline u32 net_hash_flow6(const net_addr_flow6 *n)
static inline u32 net_hash_ip6_sadr(const net_addr_ip6_sadr *n)
{ return px6_hash(n->dst_prefix, n->dst_pxlen); }
+static inline u32 net_hash_eth(const net_addr_eth *n)
+{ u64 x = 0; memcpy(&x, (char *) n + OFFSETOF(net_addr_eth, mac), 8); return
u64_hash(x); }
+
static inline u32 net_hash_mpls(const net_addr_mpls *n)
{ return u32_hash(n->label); }
static inline u32 net_hash_aspa(const net_addr_aspa *n)
{ return u32_hash(n->asn); }
+static inline u32 net_hash_evpn(const net_addr_evpn *n)
+{ return mem_hash(&n->tag, n->length - OFFSETOF(net_addr_evpn, tag)); }
+
u32 net_hash(const net_addr *a);
@@ -602,12 +766,18 @@ static inline int net_validate_flow4(const net_addr_flow4
*n)
static inline int net_validate_flow6(const net_addr_flow6 *n)
{ return net_validate_px6(n->prefix, n->pxlen); }
+static inline int net_validate_eth(const net_addr_eth *n)
+{ return n->vid < (1 << 12); }
+
static inline int net_validate_mpls(const net_addr_mpls *n)
{ return n->label < (1 << 20); }
static inline int net_validate_aspa(const net_addr_aspa *n)
{ return n->asn > 0; }
+static inline int net_validate_evpn(const net_addr_evpn *n UNUSED)
+{ return 1; /* XXX */ }
+
static inline int net_validate_ip6_sadr(const net_addr_ip6_sadr *n)
{ return net_validate_px6(n->dst_prefix, n->dst_pxlen) &&
net_validate_px6(n->src_prefix, n->src_pxlen); }
diff --git a/lib/printf.c b/lib/printf.c
index 0d2f95e8..01cf20fe 100644
--- a/lib/printf.c
+++ b/lib/printf.c
@@ -230,6 +230,24 @@ int bvsnprintf(char *buf, int size, const char *fmt,
va_list args)
if (field_width > size)
return -1;
switch (*fmt) {
+ case 'b': {
+ const char *digits="0123456789abcdef";
+ const byte *bs = va_arg(args, const byte *);
+ len = field_width;
+
+ if (3*len > size)
+ return -1;
+
+ for (i = 0; i < len; i++) {
+ const byte b = *bs++;
+ *str++ = digits[b >> 4];
+ *str++ = digits[b & 0xf];
+ *str++ = ':';
+ }
+
+ str -= !!i;
+ continue;
+ }
case 'c':
if (!(flags & LEFT))
while (--field_width > 0)
diff --git a/nest/config.Y b/nest/config.Y
index 4a3b45b5..991a4377 100644
--- a/nest/config.Y
+++ b/nest/config.Y
@@ -115,7 +115,7 @@ CF_DECLS
CF_KEYWORDS(ROUTER, ID, HOSTNAME, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED,
DEBUG, ALL, OFF, DIRECT)
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, ASPA)
+CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, ETH, 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)
@@ -132,7 +132,7 @@ 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, ASPA)
-CF_ENUM(T_ENUM_NET_TYPE, NET_, IP4, IP6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6,
IP6_SADR, MPLS, ASPA)
+CF_ENUM(T_ENUM_NET_TYPE, NET_, IP4, IP6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6,
IP6_SADR, ETH, MPLS, ASPA, EVPN)
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,
AGGREGATED)
@@ -142,6 +142,7 @@ 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)
+CF_ENUM(T_ENUM_EVPN_TYPE, NET_EVPN_, EAD, MAC, IMET, ES)
%type <f> imexport
%type <r> rtable
@@ -191,7 +192,9 @@ net_type_base:
| ROA6 { $$ = NET_ROA6; }
| FLOW4{ $$ = NET_FLOW4; }
| FLOW6{ $$ = NET_FLOW6; }
+ | ETH { $$ = NET_ETH; }
| ASPA { $$ = NET_ASPA; }
+ | EVPN { $$ = NET_EVPN; }
;
net_type:
diff --git a/nest/protocol.h b/nest/protocol.h
index 97a2ba40..c2e7f02c 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -45,8 +45,10 @@ enum protocol_class {
PROTOCOL_BFD,
PROTOCOL_BGP,
PROTOCOL_BMP,
+ PROTOCOL_BRIDGE,
PROTOCOL_DEVICE,
PROTOCOL_DIRECT,
+ PROTOCOL_EVPN,
PROTOCOL_KERNEL,
PROTOCOL_L3VPN,
PROTOCOL_OSPF,
@@ -105,8 +107,8 @@ void protos_dump_all(struct dump_request *);
*/
extern struct protocol
- proto_device, proto_radv, proto_rip, proto_static, proto_mrt,
- proto_ospf, proto_perf, proto_l3vpn, proto_aggregator,
+ proto_device, proto_radv, proto_rip, proto_static, proto_mrt, proto_evpn,
+ proto_ospf, proto_perf, proto_l3vpn, proto_aggregator, proto_bridge,
proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki;
/*
diff --git a/nest/route.h b/nest/route.h
index 5a9e7fa1..80c558b8 100644
--- a/nest/route.h
+++ b/nest/route.h
@@ -484,7 +484,9 @@ typedef struct rta {
#define RTS_PERF 15 /* Perf checker */
#define RTS_L3VPN 16 /* MPLS L3VPN */
#define RTS_AGGREGATED 17 /* Aggregated route */
-#define RTS_MAX 18
+#define RTS_BRIDGE 18
+#define RTS_EVPN 19
+#define RTS_MAX 20
#define RTD_NONE 0 /* Undefined next hop */
#define RTD_UNICAST 1 /* Next hop is neighbor router */
diff --git a/nest/rt-attr.c b/nest/rt-attr.c
index e10e1ecb..6aa74d3d 100644
--- a/nest/rt-attr.c
+++ b/nest/rt-attr.c
@@ -78,6 +78,8 @@ const char * const rta_src_names[RTS_MAX] = {
[RTS_PERF] = "Perf",
[RTS_L3VPN] = "L3VPN",
[RTS_AGGREGATED] = "aggregated",
+ [RTS_BRIDGE] = "bridge",
+ [RTS_EVPN] = "EVPN",
};
const char * rta_dest_names[RTD_MAX] = {
@@ -1324,7 +1326,7 @@ rta_dump(struct dump_request *dreq, rta *a)
"RTS_STAT_DEV", "RTS_REDIR", "RTS_RIP",
"RTS_OSPF", "RTS_OSPF_IA", "RTS_OSPF_EXT1",
"RTS_OSPF_EXT2", "RTS_BGP", "RTS_PIPE", "RTS_BABEL",
- "RTS_RPKI", "RTS_PERF", "RTS_AGGREGATED", };
+ "RTS_RPKI", "RTS_PERF", "RTS_AGGREGATED",
"RTS_BRIDGE", "RTS_EVPN" };
static char *rtd[] = { "", " DEV", " HOLE", " UNREACH", " PROHIBIT" };
RDUMP("pref=%d uc=%d %s %s%s h=%04x",
diff --git a/nest/rt-fib.c b/nest/rt-fib.c
index 688d0b96..1eb5cc7f 100644
--- a/nest/rt-fib.c
+++ b/nest/rt-fib.c
@@ -278,8 +278,10 @@ fib_find(struct fib *f, const net_addr *a)
case NET_FLOW4: return FIB_FIND(f, a, flow4);
case NET_FLOW6: return FIB_FIND(f, a, flow6);
case NET_IP6_SADR: return FIB_FIND(f, a, ip6_sadr);
+ case NET_ETH: return FIB_FIND(f, a, eth);
case NET_MPLS: return FIB_FIND(f, a, mpls);
case NET_ASPA: return FIB_FIND(f, a, aspa);
+ case NET_EVPN: return FIB_FIND(f, a, evpn);
default: bug("invalid type");
}
}
@@ -300,8 +302,10 @@ fib_insert(struct fib *f, const net_addr *a, struct
fib_node *e)
case NET_FLOW4: FIB_INSERT(f, a, e, flow4); return;
case NET_FLOW6: FIB_INSERT(f, a, e, flow6); return;
case NET_IP6_SADR: FIB_INSERT(f, a, e, ip6_sadr); return;
+ case NET_ETH: FIB_INSERT(f, a, e, eth); return;
case NET_MPLS: FIB_INSERT(f, a, e, mpls); return;
case NET_ASPA: FIB_INSERT(f, a, e, aspa); return;
+ case NET_EVPN: FIB_INSERT(f, a, e, evpn); return;
default: bug("invalid type");
}
}
diff --git a/nest/rt-table.c b/nest/rt-table.c
index ed364d35..239ce764 100644
--- a/nest/rt-table.c
+++ b/nest/rt-table.c
@@ -1103,6 +1103,10 @@ rte_validate(rte *e)
if (net_is_flow(n->n.addr) && (e->attrs->dest == RTD_UNREACHABLE))
return 1;
+ /* XXX hack */
+ if (n->n.addr->type == NET_EVPN)
+ 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;
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;
diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c
index e853624b..bdead04f 100644
--- a/proto/bgp/attrs.c
+++ b/proto/bgp/attrs.c
@@ -203,6 +203,30 @@ bgp_encode_raw(struct bgp_write_state *s UNUSED, eattr *a,
byte *buf, uint size)
}
+/*
+ * PMSI tunnel handling
+ */
+
+adata *
+bgp_pmsi_new_ingress_replication(linpool *pool, ip_addr addr, u32 label)
+{
+ int v4 = ipa_is_ip4(addr);
+ uint dlen = 5 + (v4 ? sizeof(ip4_addr) : sizeof(ip6_addr));
+ adata *ad = lp_alloc_adata(pool, dlen);
+
+ ad->data[0] = 0;
+ ad->data[1] = BGP_PMSI_TYPE_INGRESS_REPLICATION;
+ put_u24(ad->data + 2, label);
+
+ if (v4)
+ put_ip4(ad->data + 5, ipa_to_ip4(addr));
+ else
+ put_ip6(ad->data + 5, ipa_to_ip6(addr));
+
+ return ad;
+}
+
+
/*
* AIGP handling
*/
@@ -849,6 +873,64 @@ bgp_decode_as4_path(struct bgp_parse_state *s, uint code
UNUSED, uint flags, byt
}
+static void
+bgp_decode_pmsi_tunnel(struct bgp_parse_state *s, uint code UNUSED, uint
flags, byte *data, uint len, ea_list **to)
+{
+ if (len < 5)
+ WITHDRAW(BAD_LENGTH, "PMSI_TUNNEL", len);
+
+ uint dlen = len - 5;
+
+ switch (data[1])
+ {
+ case BGP_PMSI_TYPE_NO_INFO:
+ if (dlen != 0)
+ WITHDRAW(BAD_LENGTH, "PMSI_TUNNEL", len);
+ break;
+
+ case BGP_PMSI_TYPE_INGRESS_REPLICATION:
+ if ((dlen != sizeof(ip4_addr)) && (dlen != sizeof(ip6_addr)))
+ WITHDRAW(BAD_LENGTH, "PMSI_TUNNEL", len);
+ break;
+
+ default:
+ flags |= BAF_PARTIAL;
+ }
+
+ bgp_set_attr_data(to, s->pool, BA_PMSI_TUNNEL, flags, data, len);
+}
+
+static void
+bgp_format_pmsi_tunnel(const eattr *a, byte *buf, uint size)
+{
+ const adata *ad = a->u.ptr;
+ uint type = bgp_pmsi_get_type(ad);
+ uint label = bgp_pmsi_get_label(ad);
+
+ char mpls[16] = {};
+ if (label)
+ bsprintf(mpls, " mpls %u", label);
+
+ switch (type)
+ {
+ case BGP_PMSI_TYPE_NO_INFO:
+ bsnprintf(buf, size, "no-info%s", mpls);
+ break;
+
+ case BGP_PMSI_TYPE_INGRESS_REPLICATION:;
+ ip_addr a = bgp_pmsi_ir_get_endpoint(ad);
+ bsnprintf(buf, size, "ingress-replication %I%s", a, mpls);
+ break;
+
+ default:;
+ int n = bsnprintf(buf, size, "type %u%s ", type, mpls);
+ ADVANCE(buf, size, n);
+ bstrbintohex(ad->data + 5, ad->length - 5, buf, size, ':');
+ break;
+ }
+}
+
+
static void
bgp_export_aigp(struct bgp_export_state *s, eattr *a)
{
@@ -1112,6 +1194,14 @@ static const struct bgp_attr_desc bgp_attr_table[] = {
.decode = bgp_decode_as4_aggregator,
.format = bgp_format_aggregator,
},
+ [BA_PMSI_TUNNEL] = {
+ .name = "pmsi_tunnel",
+ .type = EAF_TYPE_OPAQUE,
+ .flags = BAF_OPTIONAL | BAF_TRANSITIVE,
+ .encode = bgp_encode_raw,
+ .decode = bgp_decode_pmsi_tunnel,
+ .format = bgp_format_pmsi_tunnel,
+ },
[BA_AIGP] = {
.name = "aigp",
.type = EAF_TYPE_OPAQUE,
diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c
index 0a68acb9..03f91730 100644
--- a/proto/bgp/bgp.c
+++ b/proto/bgp/bgp.c
@@ -2593,6 +2593,9 @@ bgp_channel_start(struct channel *C)
if (bgp_channel_is_ipv6(c) && (ipa_is_ip6(src) || c->ext_next_hop))
c->next_hop_addr = src;
+
+ if (bgp_channel_is_l2vpn(c))
+ c->next_hop_addr = src;
}
/* Use preferred addresses associated with interface / source address */
@@ -2945,10 +2948,10 @@ bgp_postconfig(struct proto_config *CF)
/* Default values of IGP tables */
if ((cc->gw_mode == GW_RECURSIVE) && !cc->desc->no_igp)
{
- if (!cc->igp_table_ip4 && (bgp_cc_is_ipv4(cc) || cc->ext_next_hop))
+ if (!cc->igp_table_ip4 && (bgp_cc_is_ipv4(cc) || bgp_cc_is_l2vpn(cc) ||
cc->ext_next_hop))
cc->igp_table_ip4 = bgp_default_igp_table(cf, cc, NET_IP4);
- if (!cc->igp_table_ip6 && (bgp_cc_is_ipv6(cc) || cc->ext_next_hop))
+ if (!cc->igp_table_ip6 && (bgp_cc_is_ipv6(cc) || bgp_cc_is_l2vpn(cc) ||
cc->ext_next_hop))
cc->igp_table_ip6 = bgp_default_igp_table(cf, cc, NET_IP6);
if (cc->igp_table_ip4 && bgp_cc_is_ipv6(cc) && !cc->ext_next_hop)
@@ -3620,7 +3623,7 @@ struct protocol proto_bgp = {
.template = "bgp%d",
.class = PROTOCOL_BGP,
.preference = DEF_PREF_BGP,
- .channel_mask = NB_IP | NB_VPN | NB_FLOW | NB_MPLS,
+ .channel_mask = NB_IP | NB_VPN | NB_FLOW | NB_EVPN | NB_MPLS,
.proto_size = sizeof(struct bgp_proto),
.config_size = sizeof(struct bgp_config),
.postconfig = bgp_postconfig,
diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h
index 9a206cfe..f6d303b1 100644
--- a/proto/bgp/bgp.h
+++ b/proto/bgp/bgp.h
@@ -27,10 +27,12 @@ struct eattr;
#define BGP_AFI_IPV4 1
#define BGP_AFI_IPV6 2
+#define BGP_AFI_L2VPN 25
#define BGP_SAFI_UNICAST 1
#define BGP_SAFI_MULTICAST 2
#define BGP_SAFI_MPLS 4
+#define BGP_SAFI_EVPN 70
#define BGP_SAFI_MPLS_VPN 128
#define BGP_SAFI_VPN_MULTICAST 129
#define BGP_SAFI_FLOW 133
@@ -53,6 +55,7 @@ struct eattr;
#define BGP_AF_VPN6_MC BGP_AF( BGP_AFI_IPV6, BGP_SAFI_VPN_MULTICAST )
#define BGP_AF_FLOW4 BGP_AF( BGP_AFI_IPV4, BGP_SAFI_FLOW )
#define BGP_AF_FLOW6 BGP_AF( BGP_AFI_IPV6, BGP_SAFI_FLOW )
+#define BGP_AF_EVPN BGP_AF( BGP_AFI_L2VPN, BGP_SAFI_EVPN )
struct bgp_write_state;
@@ -582,12 +585,18 @@ static inline int bgp_channel_is_ipv4(struct bgp_channel
*c)
static inline int bgp_channel_is_ipv6(struct bgp_channel *c)
{ return BGP_AFI(c->afi) == BGP_AFI_IPV6; }
+static inline int bgp_channel_is_l2vpn(struct bgp_channel *c)
+{ return BGP_AFI(c->afi) == BGP_AFI_L2VPN; }
+
static inline int bgp_cc_is_ipv4(struct bgp_channel_config *c)
{ return BGP_AFI(c->afi) == BGP_AFI_IPV4; }
static inline int bgp_cc_is_ipv6(struct bgp_channel_config *c)
{ return BGP_AFI(c->afi) == BGP_AFI_IPV6; }
+static inline int bgp_cc_is_l2vpn(struct bgp_channel_config *c)
+{ return BGP_AFI(c->afi) == BGP_AFI_L2VPN; }
+
static inline int bgp_channel_is_role_applicable(struct bgp_channel *c)
{ return (c->afi == BGP_AF_IPV4 || c->afi == BGP_AF_IPV6); }
@@ -701,10 +710,37 @@ void bgp_rt_notify(struct proto *P, struct channel *C,
net *n, rte *new, rte *ol
int bgp_preexport(struct channel *, struct rte *);
int bgp_get_attr(const struct eattr *e, byte *buf, int buflen);
void bgp_get_route_info(struct rte *, byte *buf);
+adata * bgp_pmsi_new_ingress_replication(linpool *pool, ip_addr addr, u32
label);
int bgp_total_aigp_metric_(rte *e, u64 *metric, const struct adata **ad);
byte * bgp_bmp_encode_rte(struct bgp_channel *c, byte *buf, byte *end, const
net_addr *n, const struct rte *new, const struct rte_src *src);
+#define BGP_PMSI_TYPE_NO_INFO 0
+#define BGP_PMSI_TYPE_INGRESS_REPLICATION 6
+
+static inline uint
+bgp_pmsi_get_type(const adata *ad)
+{ return ad->data[1]; }
+
+static inline u32
+bgp_pmsi_get_label(const adata *ad)
+{ return get_u24(ad->data + 2); }
+
+static inline ip_addr
+bgp_pmsi_ir_get_endpoint(const adata *ad)
+{
+ uint dlen = ad->length - 5;
+ const byte *data = ad->data + 5;
+
+ if (dlen == sizeof(ip4_addr))
+ return ipa_from_ip4(get_ip4(data));
+ else if (dlen == sizeof(ip6_addr))
+ return ipa_from_ip6(get_ip6(data));
+ else
+ return IPA_NONE;
+}
+
+
#define BGP_AIGP_METRIC 1
#define BGP_AIGP_MAX U64(0xffffffffffffffff)
@@ -771,6 +807,7 @@ byte *bgp_create_end_mark_(struct bgp_channel *c, byte
*buf);
#define BA_EXT_COMMUNITY 0x10 /* RFC 4360 */
#define BA_AS4_PATH 0x11 /* RFC 6793 */
#define BA_AS4_AGGREGATOR 0x12 /* RFC 6793 */
+#define BA_PMSI_TUNNEL 0x16 /* RFC 6514 */
#define BA_AIGP 0x1a /* RFC 7311 */
#define BA_LARGE_COMMUNITY 0x20 /* RFC 8092 */
#define BA_ONLY_TO_CUSTOMER 0x23 /* RFC 9234 */
diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y
index 4ffc49f6..7cf499bc 100644
--- a/proto/bgp/config.Y
+++ b/proto/bgp/config.Y
@@ -36,7 +36,7 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT,
RETRY, KEEPALIVE,
DYNAMIC, RANGE, NAME, DIGITS, BGP_AIGP, AIGP, ORIGINATE, COST, ENFORCE,
FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER,
RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND, RECV, MIN, MAX,
- AUTHENTICATION, NONE, MD5, AO, FORMAT, NATIVE, SINGLE, DOUBLE)
+ AUTHENTICATION, NONE, MD5, AO, FORMAT, NATIVE, SINGLE, DOUBLE,
BGP_PMSI_TUNNEL)
CF_KEYWORDS(KEY, KEYS, SECRET, DEPRECATED, PREFERRED, ALGORITHM, CMAC, AES128)
@@ -265,6 +265,7 @@ bgp_afi:
| VPN6 MULTICAST { $$ = BGP_AF_VPN6_MC; }
| FLOW4 { $$ = BGP_AF_FLOW4; }
| FLOW6 { $$ = BGP_AF_FLOW6; }
+ | EVPN { $$ = BGP_AF_EVPN; }
;
tcp_ao_key_start: KEY {
@@ -520,6 +521,8 @@ dynamic_attr: BGP_CLUSTER_LIST
{ $$ = f_new_dynamic_attr(EAF_TYPE_INT_SET, T_CLIST,
EA_CODE(PROTOCOL_BGP, BA_CLUSTER_LIST)); $$.flags = BAF_OPTIONAL; } ;
dynamic_attr: BGP_EXT_COMMUNITY
{ $$ = f_new_dynamic_attr(EAF_TYPE_EC_SET, T_ECLIST,
EA_CODE(PROTOCOL_BGP, BA_EXT_COMMUNITY)); $$.flags = BAF_OPTIONAL |
BAF_TRANSITIVE; } ;
+dynamic_attr: BGP_PMSI_TUNNEL
+ { $$ = f_new_dynamic_attr(EAF_TYPE_OPAQUE, T_ENUM_EMPTY,
EA_CODE(PROTOCOL_BGP, BA_PMSI_TUNNEL)); } ;
dynamic_attr: BGP_AIGP
{ $$ = f_new_dynamic_attr(EAF_TYPE_OPAQUE, T_ENUM_EMPTY,
EA_CODE(PROTOCOL_BGP, BA_AIGP)); $$.flags = BAF_OPTIONAL; } ;
dynamic_attr: BGP_LARGE_COMMUNITY
diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c
index fcaff745..2c2edb08 100644
--- a/proto/bgp/packets.c
+++ b/proto/bgp/packets.c
@@ -34,6 +34,7 @@
#define BGP_RR_END 2
#define BGP_NLRI_MAX (4 + 1 + 32)
+#define BGP_NLRI_EVPN_MAX (4 + 2 + 52)
#define BGP_MPLS_BOS 1 /* Bottom-of-stack bit */
#define BGP_MPLS_MAX 10 /* Max number of labels that 24*n <=
255 */
@@ -1075,6 +1076,25 @@ bgp_rx_open(struct bgp_conn *conn, byte *pkt, uint len)
#define MISMATCHED_AF " - mismatched address family (%I for %s)"
+static int
+bgp_channel_match_next_hop_af(struct bgp_channel *c, ip_addr nh)
+{
+ switch (BGP_AFI(c->afi))
+ {
+ case BGP_AFI_IPV4:
+ return ipa_is_ip4(nh) || c->ext_next_hop;
+
+ case BGP_AFI_IPV6:
+ return ipa_is_ip6(nh) || c->ext_next_hop;
+
+ case BGP_AFI_L2VPN:
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
static void
bgp_apply_next_hop(struct bgp_parse_state *s, rta *a, ip_addr gw, ip_addr ll)
{
@@ -1308,12 +1328,11 @@ bgp_update_next_hop_ip(struct bgp_export_state *s,
eattr *a, ea_list **to)
REJECT(BAD_NEXT_HOP " - neighbor address %I", peer);
/* Forbid next hop with non-matching AF */
- if ((ipa_is_ip4(nh[0]) != bgp_channel_is_ipv4(s->channel)) &&
- !s->channel->ext_next_hop)
+ if (!bgp_channel_match_next_hop_af(s->channel, nh[0]))
REJECT(BAD_NEXT_HOP MISMATCHED_AF, nh[0], s->channel->desc->name);
- /* Just check if MPLS stack */
- if (s->mpls && !bgp_find_attr(*to, BA_MPLS_LABEL_STACK))
+ /* Just check if there is MPLS stack - not applicable for EVPN */
+ if (s->mpls && (s->channel->afi != BGP_AF_EVPN) && !bgp_find_attr(*to,
BA_MPLS_LABEL_STACK))
REJECT(NO_LABEL_STACK);
}
@@ -1422,7 +1441,7 @@ bgp_decode_next_hop_ip(struct bgp_parse_state *s, byte
*data, uint len, rta *a)
if (ipa_zero(nh[1]))
ad->length = 16;
- if ((bgp_channel_is_ipv4(c) != ipa_is_ip4(nh[0])) && !c->ext_next_hop)
+ if (!bgp_channel_match_next_hop_af(c, nh[0]))
WITHDRAW(BAD_NEXT_HOP MISMATCHED_AF, nh[0], c->desc->name);
// XXXX validate next hop
@@ -1513,7 +1532,7 @@ bgp_decode_next_hop_vpn(struct bgp_parse_state *s, byte
*data, uint len, rta *a)
if ((get_u64(data) != 0) || ((len == 48) && (get_u64(data+24) != 0)))
bgp_parse_error(s, 9);
- if ((bgp_channel_is_ipv4(c) != ipa_is_ip4(nh[0])) && !c->ext_next_hop)
+ if (!bgp_channel_match_next_hop_af(c, nh[0]))
WITHDRAW(BAD_NEXT_HOP MISMATCHED_AF, nh[0], c->desc->name);
// XXXX validate next hop
@@ -2222,6 +2241,407 @@ bgp_decode_nlri_flow6(struct bgp_parse_state *s, byte
*pos, uint len, rta *a)
}
}
+static inline void
+bgp_encode_evpn_ip(byte **pos, uint *size, ip_addr ip)
+{
+ if (ipa_is_ip4(ip))
+ {
+ **pos = IP4_MAX_PREFIX_LENGTH;
+ put_ip4(*pos+1, ipa_to_ip4(ip));
+ ADVANCE(*pos, *size, 1+4);
+ }
+ else
+ {
+ **pos = IP6_MAX_PREFIX_LENGTH;
+ put_ip6(*pos+1, ipa_to_ip6(ip));
+ ADVANCE(*pos, *size, 1+16);
+ }
+}
+
+static inline ip_addr
+bgp_decode_evpn_ip(struct bgp_parse_state *s, byte **pos, uint *len)
+{
+ uint alen = **pos; /* Assume this is validated by caller */
+ uint blen = 1 + (alen >> 3);
+
+ if (*len < blen)
+ bgp_parse_error(s, 1);
+
+ ip_addr ip;
+ if (alen == IP4_MAX_PREFIX_LENGTH)
+ ip = ipa_from_ip4(get_ip4(*pos + 1));
+ else if (alen == IP6_MAX_PREFIX_LENGTH)
+ ip = ipa_from_ip6(get_ip6(*pos + 1));
+ else
+ bgp_parse_error(s, 10); /* ? */
+
+ ADVANCE(*pos, *len, blen);
+ return ip;
+}
+
+static inline u32 bgp_label_ready(const adata *m, uint pos)
+{ return m && (m->length >= 4*(pos+1)); }
+
+static inline u32 bgp_get_label_(const adata *m, uint pos)
+{ return ((u32 *) m->data)[pos]; }
+
+static inline u32 bgp_get_label(const adata *m, uint pos)
+{ return bgp_label_ready(m, pos) ? bgp_get_label_(m, pos) : 0; }
+
+static uint
+bgp_encode_evpn_ead(struct bgp_write_state *s UNUSED, const net_addr_evpn
*net, byte *buf, uint size)
+{
+ byte *pos = buf;
+
+ /* Encode route distinguisher */
+ put_u64(pos, net->rd);
+ ADVANCE(pos, size, 8);
+
+ /* Encode ethernet segment ID */
+ memcpy(pos, &net->ead.esi, 10);
+ ADVANCE(pos, size, 10);
+
+ /* Encode ethernet tag ID */
+ put_u32(pos, net->tag);
+ ADVANCE(pos, size, 4);
+
+ /* Encode MPLS label */
+ u32 label = bgp_get_label(s->mpls_labels, 0);
+ put_u24(pos, label); // << 4);
+ ADVANCE(pos, size, 3);
+
+ return pos - buf;
+}
+
+static void
+bgp_decode_evpn_ead(struct bgp_parse_state *s, net_addr_evpn *net, byte *pos,
uint len)
+{
+ if (len < (8+10+4+3))
+ bgp_parse_error(s, 1);
+
+ /* Decode route distinguisher */
+ u64 rd = get_u64(pos);
+ ADVANCE(pos, len, 8);
+
+ /* Decode ethernet segment ID */
+ evpn_esi esi;
+ memcpy(&esi, pos, 10);
+ ADVANCE(pos, len, 10);
+
+ /* Decode ethernet tag ID */
+ u32 tag = get_u32(pos);
+ ADVANCE(pos, len, 4);
+
+ /* Decode MPLS label */
+ u32 label = get_u24(pos); // >> 4;
+ ADVANCE(pos, len, 3);
+
+ s->mpls_labels = lp_alloc_adata(s->pool, 4);
+ memcpy(s->mpls_labels->data, &label, 4);
+
+ if (len)
+ bgp_parse_error(s, 1);
+
+ net->ead = NET_ADDR_EVPN_EAD(rd, tag, esi);
+}
+
+static uint
+bgp_encode_evpn_mac(struct bgp_write_state *s UNUSED, const net_addr_evpn
*net, byte *buf, uint size)
+{
+ byte *pos = buf;
+
+ /* Encode route distinguisher */
+ put_u64(pos, net->rd);
+ ADVANCE(pos, size, 8);
+
+ /* Encode ethernet segment ID - XXX */
+ memset(pos, 0, 10);
+ ADVANCE(pos, size, 10);
+
+ /* Encode ethernet tag ID */
+ put_u32(pos, net->tag);
+ ADVANCE(pos, size, 4);
+
+ /* Encode MAC address */
+ pos[0] = 48;
+ memcpy(pos+1, &net->mac.mac, 6);
+ ADVANCE(pos, size, 7);
+
+ /* Encode IP address */
+ pos[0] = 0;
+ if (net->length == sizeof(net_addr_evpn_mac_ip))
+ bgp_encode_evpn_ip(&pos, &size, net->mac_ip.ip);
+ else
+ ADVANCE(pos, size, 1);
+
+ /* Encode MPLS label */
+ u32 label1 = bgp_get_label(s->mpls_labels, 0);
+ put_u24(pos, label1); // << 4);
+ ADVANCE(pos, size, 3);
+
+ if (bgp_label_ready(s->mpls_labels, 1))
+ {
+ u32 label2 = bgp_get_label_(s->mpls_labels, 1);
+ put_u24(pos, label2); // << 4);
+ ADVANCE(pos, size, 3);
+ }
+
+ return pos - buf;
+}
+
+static void
+bgp_decode_evpn_mac(struct bgp_parse_state *s, net_addr_evpn *net, byte *pos,
uint len)
+{
+ if (len < (8+10+4+7+1))
+ bgp_parse_error(s, 1);
+
+ /* Decode route distinguisher */
+ u64 rd = get_u64(pos);
+ ADVANCE(pos, len, 8);
+
+ /* Decode ethernet segment ID - XXX */
+ evpn_esi esi;
+ memcpy(&esi, pos, 10);
+ ADVANCE(pos, len, 10);
+
+ /* Decode ethernet tag ID */
+ u32 tag = get_u32(pos);
+ ADVANCE(pos, len, 4);
+
+ /* Decode MAC address */
+ if (pos[0] != 48)
+ bgp_parse_error(s, 10); /* ? */
+
+ mac_addr mac;
+ memcpy(&mac, pos+1, 6);
+ ADVANCE(pos, len, 7);
+
+ /* Decode IP address */
+ ip_addr ip = IPA_NONE;
+ if (pos[0])
+ ip = bgp_decode_evpn_ip(s, &pos, &len);
+ else
+ ADVANCE(pos, len, 1);
+
+ /* Decode MPLS labels */
+ if (len < 3)
+ bgp_parse_error(s, 1);
+
+ u32 label[2], lnum = 1;
+ label[0] = get_u24(pos); // >> 4;
+ ADVANCE(pos, len, 3);
+
+ if (len >= 3)
+ {
+ label[1] = get_u24(pos); // >> 4;
+ ADVANCE(pos, len, 3);
+ lnum++;
+ }
+
+ s->mpls_labels = lp_alloc_adata(s->pool, 4 * lnum);
+ memcpy(s->mpls_labels->data, label, 4 * lnum);
+
+ if (len)
+ bgp_parse_error(s, 1);
+
+ if (ipa_zero(ip))
+ net->mac = NET_ADDR_EVPN_MAC(rd, tag, mac);
+ else
+ net->mac_ip = NET_ADDR_EVPN_MAC_IP(rd, tag, mac, ip);
+}
+
+static uint
+bgp_encode_evpn_imet(struct bgp_write_state *s UNUSED, const net_addr_evpn
*net, byte *buf, uint size)
+{
+ byte *pos = buf;
+
+ /* Encode route distinguisher */
+ put_u64(pos, net->rd);
+ ADVANCE(pos, size, 8);
+
+ /* Encode ethernet tag ID */
+ put_u32(pos, net->tag);
+ ADVANCE(pos, size, 4);
+
+ /* Encode router IP address */
+ bgp_encode_evpn_ip(&pos, &size, net->imet.rtr);
+
+ return pos - buf;
+}
+
+static void
+bgp_decode_evpn_imet(struct bgp_parse_state *s, net_addr_evpn *net, byte *pos,
uint len)
+{
+ if (len < (8+4+1))
+ bgp_parse_error(s, 1);
+
+ /* Decode route distinguisher */
+ u64 rd = get_u64(pos);
+ ADVANCE(pos, len, 8);
+
+ /* Decode ethernet tag ID */
+ u32 tag = get_u32(pos);
+ ADVANCE(pos, len, 4);
+
+ /* Decode router IP address */
+ ip_addr rtr = bgp_decode_evpn_ip(s, &pos, &len);
+
+ if (len)
+ bgp_parse_error(s, 1);
+
+ net->imet = NET_ADDR_EVPN_IMET(rd, tag, rtr);
+}
+
+static uint
+bgp_encode_evpn_es(struct bgp_write_state *s UNUSED, const net_addr_evpn *net,
byte *buf, uint size)
+{
+ byte *pos = buf;
+
+ /* Encode route distinguisher */
+ put_u64(pos, net->rd);
+ ADVANCE(pos, size, 8);
+
+ /* Encode ethernet segment ID */
+ memcpy(pos, &net->es.esi, 10);
+ ADVANCE(pos, size, 10);
+
+ /* Encode router IP address */
+ bgp_encode_evpn_ip(&pos, &size, net->es.rtr);
+
+ return pos - buf;
+}
+
+static void
+bgp_decode_evpn_es(struct bgp_parse_state *s, net_addr_evpn *net, byte *pos,
uint len)
+{
+ if (len < (8+10+1))
+ bgp_parse_error(s, 1);
+
+ /* Decode route distinguisher */
+ u64 rd = get_u64(pos);
+ ADVANCE(pos, len, 8);
+
+ /* Decode ethernet segment ID */
+ evpn_esi esi;
+ memcpy(&esi, pos, 10);
+ ADVANCE(pos, len, 10);
+
+ /* Decode router IP address */
+ ip_addr rtr = bgp_decode_evpn_ip(s, &pos, &len);
+
+ if (len)
+ bgp_parse_error(s, 1);
+
+ net->es = NET_ADDR_EVPN_ES(rd, esi, rtr);
+}
+
+static uint
+bgp_encode_nlri_evpn(struct bgp_write_state *s, struct bgp_bucket *buck, byte
*buf, uint size)
+{
+ byte *pos = buf;
+
+ while (!EMPTY_LIST(buck->prefixes) && (size >= BGP_NLRI_EVPN_MAX))
+ {
+ struct bgp_prefix *px = HEAD(buck->prefixes);
+ const net_addr_evpn *net = (void *) px->net;
+
+ /* Encode path ID */
+ if (s->add_path)
+ {
+ put_u32(pos, px->path_id);
+ ADVANCE(pos, size, 4);
+ }
+
+ /* Encode EVPN header */
+ pos[0] = net->subtype;
+ pos[1] = 0;
+ ADVANCE(pos, size, 2);
+
+ uint rlen;
+ switch (net->subtype)
+ {
+ case NET_EVPN_EAD: rlen = bgp_encode_evpn_ead(s, net, pos, size); break;
+ case NET_EVPN_MAC: rlen = bgp_encode_evpn_mac(s, net, pos, size); break;
+ case NET_EVPN_IMET: rlen = bgp_encode_evpn_imet(s, net, pos, size);
break;
+ case NET_EVPN_ES: rlen = bgp_encode_evpn_es(s, net, pos, size); break;
+ }
+
+ /* Fix length */
+ pos[-1] = rlen;
+
+ ADVANCE(pos, size, rlen);
+
+ if (!s->sham)
+ bgp_free_prefix(s->channel, px);
+ else
+ rem_node(&px->buck_node);
+ }
+
+ return pos - buf;
+}
+
+static void
+bgp_decode_nlri_evpn(struct bgp_parse_state *s, byte *pos, uint len, rta *a)
+{
+ ea_list *base_eattrs = a ? a->eattrs : NULL;
+
+ while (len)
+ {
+ net_addr_evpn net;
+ u32 path_id = 0;
+
+ s->mpls_labels = NULL;
+
+ /* Reset attributes */
+ if (a)
+ a->eattrs = base_eattrs;
+
+ /* Decode path ID */
+ if (s->add_path)
+ {
+ if (len < 5)
+ bgp_parse_error(s, 1);
+
+ path_id = get_u32(pos);
+ ADVANCE(pos, len, 4);
+ }
+
+ if (len < 2)
+ bgp_parse_error(s, 1);
+
+ /* Decode EVPN header */
+ uint type = pos[0];
+ uint rlen = pos[1];
+ ADVANCE(pos, len, 2);
+
+ if (len < rlen)
+ bgp_parse_error(s, 1);
+
+ switch (type)
+ {
+ case NET_EVPN_EAD: bgp_decode_evpn_ead(s, &net, pos, rlen); break;
+ case NET_EVPN_MAC: bgp_decode_evpn_mac(s, &net, pos, rlen); break;
+ case NET_EVPN_IMET: bgp_decode_evpn_imet(s, &net, pos, rlen); break;
+ case NET_EVPN_ES: bgp_decode_evpn_es(s, &net, pos, rlen); break;
+ default: net = (net_addr_evpn){}; // XXX
+ }
+
+ ADVANCE(pos, len, rlen);
+
+ if (a && s->mpls_labels)
+ {
+ adata *m = s->mpls_labels;
+ bgp_set_attr_ptr(&(a->eattrs), s->pool, BA_MPLS_LABEL_STACK, 0, m);
+ bgp_apply_mpls_labels(s, a, (u32 *) m->data, m->length / 4);
+ }
+
+ bgp_rte_update(s, (net_addr *) &net, path_id, a);
+
+ rta_free(s->cached_rta);
+ s->cached_rta = NULL;
+ }
+}
+
static const struct bgp_af_desc bgp_af_table[] = {
{
@@ -2350,6 +2770,17 @@ static const struct bgp_af_desc bgp_af_table[] = {
.decode_next_hop = bgp_decode_next_hop_none,
.update_next_hop = bgp_update_next_hop_none,
},
+ {
+ .afi = BGP_AF_EVPN,
+ .net = NET_EVPN,
+ .mpls = 1,
+ .name = "evpn",
+ .encode_nlri = bgp_encode_nlri_evpn,
+ .decode_nlri = bgp_decode_nlri_evpn,
+ .encode_next_hop = bgp_encode_next_hop_ip,
+ .decode_next_hop = bgp_decode_next_hop_ip,
+ .update_next_hop = bgp_update_next_hop_ip,
+ },
};
const struct bgp_af_desc *
diff --git a/proto/bridge/Doc b/proto/bridge/Doc
new file mode 100644
index 00000000..ca0136f9
--- /dev/null
+++ b/proto/bridge/Doc
@@ -0,0 +1 @@
+S bridge.c
diff --git a/proto/bridge/Makefile b/proto/bridge/Makefile
new file mode 100644
index 00000000..82755ee7
--- /dev/null
+++ b/proto/bridge/Makefile
@@ -0,0 +1,6 @@
+src := bridge.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
+
+tests_objs := $(tests_objs) $(src-o-files)
diff --git a/proto/bridge/bridge.c b/proto/bridge/bridge.c
new file mode 100644
index 00000000..5e0a8488
--- /dev/null
+++ b/proto/bridge/bridge.c
@@ -0,0 +1,285 @@
+/*
+ * BIRD -- Linux Bridge Interface
+ *
+ * (c) 2023 Ondrej Zajicek <[email protected]>
+ * (c) 2023 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: Bridge
+ *
+ * The Bridge protocol is responsible for synchronization of BIRD ethernet
+ * table with Linux kernel bridge interface (although the code is mostly
+ * OS-independent, as Linux-specific parts are in the Netlink code). It is
+ * similar to (and based on) the Kernel protocol, but the differences are
+ * large enough to treat it as an independent protocol.
+ */
+
+/*
+ * TODO:
+ * - Better two-way synchronization, including initial clean-up
+ * - Wait for existence (and active state) of the bridge device
+ * - Check for consistency of vlan_filtering flag
+ * - Channel should be R_ANY for BUM routes, but RA_OPTIMAL for others
+ * - Configuration of VIDs?
+ */
+
+#undef LOCAL_DEBUG
+
+#include "nest/bird.h"
+#include "nest/iface.h"
+#include "nest/protocol.h"
+#include "nest/route.h"
+#include "nest/mpls.h"
+#include "nest/cli.h"
+#include "conf/conf.h"
+#include "filter/filter.h"
+#include "filter/data.h"
+#include "lib/string.h"
+
+#include "bridge.h"
+
+void
+kbr_got_route(struct kbr_proto *p, const net_addr *n, rte *e, int src UNUSED,
int scan UNUSED)
+{
+ struct channel *c = p->p.main_channel;
+
+ if (e) e->attrs->pref = c->preference;
+ rte_update2(c, n, e, p->p.main_source);
+}
+
+static void
+kbr_scan(timer *t)
+{
+ struct kbr_proto *p = t->data;
+ struct channel *c = p->p.main_channel;
+
+ TRACE(D_EVENTS, "Scanning bridge table");
+
+ rt_refresh_begin(c->table, c);
+ kbr_do_scan(p);
+ rt_refresh_end(c->table, c);
+}
+
+static void
+kbr_rt_notify(struct proto *P, struct channel *c0 UNUSED, net *net, rte *new,
rte *old)
+{
+ struct kbr_proto *p UNUSED = (void *) P;
+
+ rte *new_gw = (new && ipa_nonzero(new->attrs->nh.gw)) ? new : NULL;
+ rte *old_gw = (old && ipa_nonzero(old->attrs->nh.gw)) ? old : NULL;
+
+ /*
+ * This code handles peculiarities of Linux bridge behavior, where the bridge
+ * device has attached both network interfaces and a tunnel (VXLAN) device.
+ * For 'remote' MAC addresses, forwarding entries in the bridge device point
+ * to the tunnel device. The tunnel device has another forwarding table with
+ * forwarding entries, this time with IP addresses of remote endpoints.
+ *
+ * BUM frames are propagated by the bridge device to all attached devices, so
+ * there is no need to have a bridge forwarding entry, but they must have a
+ * tunnel forwarding entry for each destination.
+ */
+
+ if (mac_zero(net_mac_addr(net->n.addr)))
+ {
+ /* For BUM routes, we have multiple tunnel entries, but no bridge entry */
+ kbr_update_fdb(net->n.addr, new_gw, old_gw, 1);
+ return;
+ }
+
+ /* For regular routes, we have one bridge entry, perhaps also one tunnel
entry */
+ kbr_replace_fdb(net->n.addr, new, old, 0);
+ kbr_replace_fdb(net->n.addr, new_gw, old_gw, 1);
+}
+
+static inline int
+kbr_is_installed(struct channel *c, net *n)
+{
+ return n->routes && bmap_test(&c->export_map, n->routes->id);
+}
+
+static void
+kbr_flush_routes(struct kbr_proto *p)
+{
+ struct channel *c = p->p.main_channel;
+
+ TRACE(D_EVENTS, "Flushing bridge routes");
+ FIB_WALK(&c->table->fib, net, n)
+ {
+ if (kbr_is_installed(c, n))
+ kbr_rt_notify(&p->p, c, n, NULL, n->routes);
+ }
+ FIB_WALK_END;
+}
+
+
+static int
+kbr_preexport(struct channel *C, rte *e)
+{
+ struct kbr_proto *p = (void *) C->proto;
+
+ /* Reject our own routes */
+ if (e->src->proto == &p->p)
+ return -1;
+
+ return 0;
+}
+
+static void
+kbr_reload_routes(struct channel *C)
+{
+ struct kbr_proto *p = (void *) C->proto;
+
+ tm_start(p->scan_timer, 0);
+}
+
+static inline u32
+kbr_metric(rte *e)
+{
+ u32 metric = ea_get_int(e->attrs->eattrs, EA_GEN_IGP_METRIC,
e->attrs->igp_metric);
+ return MIN(metric, IGP_METRIC_UNKNOWN);
+}
+
+static int
+kbr_rte_better(rte *new, rte *old)
+{
+ /* This is hack, we should have full BGP-style comparison */
+ return kbr_metric(new) < kbr_metric(old);
+}
+
+static void
+kbr_postconfig(struct proto_config *CF)
+{
+ struct kbr_config *cf = (void *) CF;
+
+ if (! proto_cf_main_channel(CF))
+ cf_error("Channel not specified");
+
+ if (!cf->bridge_dev)
+ cf_error("Bridge device not specified");
+}
+
+static struct proto *
+kbr_init(struct proto_config *CF)
+{
+ struct proto *P = proto_new(CF);
+ // struct kbr_proto *p = (void *) P;
+ // struct kbr_config *cf = (void *) CF;
+
+ P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF));
+
+ P->rt_notify = kbr_rt_notify;
+ P->preexport = kbr_preexport;
+ P->reload_routes = kbr_reload_routes;
+ P->rte_better = kbr_rte_better;
+
+ return P;
+}
+
+static int
+kbr_start(struct proto *P)
+{
+ struct kbr_proto *p = (void *) P;
+ struct kbr_config *cf = (void *) P->cf;
+
+ p->bridge_dev = cf->bridge_dev;
+ p->vlan_filtering = cf->vlan_filtering;
+
+ p->scan_timer = tm_new_init(p->p.pool, kbr_scan, p, cf->scan_time, 0);
+ tm_start(p->scan_timer, 100 MS);
+
+ kbr_sys_start(p);
+
+ return PS_UP;
+}
+
+static int
+kbr_shutdown(struct proto *P UNUSED)
+{
+ struct kbr_proto *p = (void *) P;
+
+ kbr_flush_routes(p);
+ kbr_sys_shutdown(p);
+
+ return PS_DOWN;
+}
+
+static int
+kbr_reconfigure(struct proto *P, struct proto_config *CF)
+{
+ struct kbr_proto *p = (void *) P;
+ struct kbr_config *cf = (void *) CF;
+
+ if ((p->bridge_dev != cf->bridge_dev) ||
+ (p->vlan_filtering != cf->vlan_filtering))
+ return 0;
+
+ if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF)))
+ return 0;
+
+ return 1;
+}
+
+static void
+kbr_copy_config(struct proto_config *dest UNUSED, struct proto_config *src
UNUSED)
+{
+ /* Just a shallow copy, not many items here */
+}
+
+const char * const kbr_src_names[KBR_SRC_MAX] = {
+ [KBR_SRC_BIRD] = "bird",
+ [KBR_SRC_LOCAL] = "local",
+ [KBR_SRC_STATIC] = "static",
+ [KBR_SRC_DYNAMIC] = "dynamic",
+};
+
+static int
+kbr_get_attr(const eattr *a, byte *buf, int buflen UNUSED)
+{
+ switch (a->id)
+ {
+ case EA_KBR_SOURCE:;
+ const char *src = (a->u.data < KBR_SRC_MAX) ? kbr_src_names[a->u.data] :
"?";
+ bsprintf(buf, "source: %s", src);
+ return GA_FULL;
+
+ default:
+ return GA_UNKNOWN;
+ }
+}
+
+static void
+kbr_get_route_info(rte *rte, byte *buf)
+{
+ eattr *a = ea_find(rte->attrs->eattrs, EA_KBR_SOURCE);
+ char src = (a && a->u.data < KBR_SRC_MAX) ? "BLSD"[a->u.data] : '?';
+
+ bsprintf(buf, " %c (%u)", src, rte->attrs->pref);
+}
+
+
+struct protocol proto_bridge = {
+ .name = "Bridge",
+ .template = "bridge%d",
+ .class = PROTOCOL_BRIDGE,
+ .channel_mask = NB_ETH,
+ .proto_size = sizeof(struct kbr_proto),
+ .config_size = sizeof(struct kbr_config),
+ .postconfig = kbr_postconfig,
+ .init = kbr_init,
+ .start = kbr_start,
+ .shutdown = kbr_shutdown,
+ .reconfigure = kbr_reconfigure,
+ .copy_config = kbr_copy_config,
+ .get_attr = kbr_get_attr,
+ .get_route_info = kbr_get_route_info,
+};
+
+void
+bridge_build(void)
+{
+ proto_build(&proto_bridge);
+}
diff --git a/proto/bridge/bridge.h b/proto/bridge/bridge.h
new file mode 100644
index 00000000..0022eae4
--- /dev/null
+++ b/proto/bridge/bridge.h
@@ -0,0 +1,53 @@
+/*
+ * BIRD -- Linux Bridge Interface
+ *
+ * (c) 2023 Ondrej Zajicek <[email protected]>
+ * (c) 2023 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_BRIDGE_H_
+#define _BIRD_BRIDGE_H_
+
+
+#define EA_KBR_SOURCE EA_CODE(PROTOCOL_BRIDGE, 0)
+
+#define KBR_SRC_BIRD 0
+#define KBR_SRC_LOCAL 1
+#define KBR_SRC_STATIC 2
+#define KBR_SRC_DYNAMIC 3
+#define KBR_SRC_MAX 4
+
+
+struct kbr_config {
+ struct proto_config c;
+
+ struct iface *bridge_dev;
+ btime scan_time;
+ int vlan_filtering;
+};
+
+struct kbr_proto {
+ struct proto p;
+
+ struct iface *bridge_dev;
+ timer *scan_timer;
+ int vlan_filtering;
+
+ struct kbr_proto *hash_next;
+};
+
+void kbr_got_route(struct kbr_proto *p, const net_addr *n, rte *e, int src,
int scan);
+
+
+/* krt sysdep */
+
+int kbr_sys_start(struct kbr_proto *p);
+void kbr_sys_shutdown(struct kbr_proto *p);
+
+void kbr_replace_fdb(const net_addr *n, rte *new, rte *old, int tunnel);
+void kbr_update_fdb(const net_addr *n, rte *new, rte *old, int tunnel);
+void kbr_do_scan(struct kbr_proto *p);
+
+#endif
diff --git a/proto/bridge/config.Y b/proto/bridge/config.Y
new file mode 100644
index 00000000..f978bda9
--- /dev/null
+++ b/proto/bridge/config.Y
@@ -0,0 +1,59 @@
+/*
+ * BIRD -- Linux Bridge Interface
+ *
+ * (c) 2023 Ondrej Zajicek <[email protected]>
+ * (c) 2023 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "proto/bridge/bridge.h"
+
+
+CF_DEFINES
+
+#define KBR_CFG ((struct kbr_config *) this_proto)
+
+
+CF_DECLS
+
+CF_KEYWORDS(BRIDGE, BRIDGE, DEVICE, VLAN, FILTERING, SCAN, TIME, KBR_SOURCE)
+
+
+CF_GRAMMAR
+
+proto: kbr_proto;
+
+
+kbr_proto_start: proto_start BRIDGE
+{
+ this_proto = proto_config_new(&proto_bridge, $1);
+ this_proto->net_type = NET_ETH;
+
+ KBR_CFG->scan_time = 60 S_;
+};
+
+kbr_proto_item:
+ proto_item
+ | proto_channel { $1->ra_mode = RA_ANY; }
+ | BRIDGE DEVICE text { KBR_CFG->bridge_dev = if_get_by_name($3); }
+ | VLAN FILTERING bool { KBR_CFG->vlan_filtering = $3; }
+ | SCAN TIME expr_us { KBR_CFG->scan_time = $3; }
+ ;
+
+kbr_proto_opts:
+ /* empty */
+ | kbr_proto_opts kbr_proto_item ';'
+ ;
+
+kbr_proto:
+ kbr_proto_start proto_name '{' kbr_proto_opts '}';
+
+
+dynamic_attr: KBR_SOURCE { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT,
EA_KBR_SOURCE); } ;
+
+CF_CODE
+
+CF_END
diff --git a/proto/evpn/Doc b/proto/evpn/Doc
new file mode 100644
index 00000000..84f282fa
--- /dev/null
+++ b/proto/evpn/Doc
@@ -0,0 +1 @@
+S evpn.c
diff --git a/proto/evpn/Makefile b/proto/evpn/Makefile
new file mode 100644
index 00000000..7c7dfc92
--- /dev/null
+++ b/proto/evpn/Makefile
@@ -0,0 +1,6 @@
+src := evpn.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
+
+tests_objs := $(tests_objs) $(src-o-files)
diff --git a/proto/evpn/config.Y b/proto/evpn/config.Y
new file mode 100644
index 00000000..db5156de
--- /dev/null
+++ b/proto/evpn/config.Y
@@ -0,0 +1,86 @@
+/*
+ * BIRD -- BGP/MPLS Ethernet Virtual Private Networks (EVPN)
+ *
+ * (c) 2023 Ondrej Zajicek <[email protected]>
+ * (c) 2023 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "proto/evpn/evpn.h"
+
+
+CF_DEFINES
+
+#define EVPN_CFG ((struct evpn_config *) this_proto)
+
+
+CF_DECLS
+
+CF_KEYWORDS(EVPN, ROUTE, IMPORT, EXPORT, TARGET, RD, DISTINGUISHER, TUNNEL,
DEVICE, VNI, VID)
+
+%type <e> evpn_targets
+%type <cc> evpn_channel_start evpn_channel
+
+
+CF_GRAMMAR
+
+proto: evpn_proto;
+
+
+evpn_channel_start: net_type_base
+{
+ /* Redefining proto_channel to change default values */
+ $$ = this_channel = channel_config_get(NULL, net_label[$1], $1, this_proto);
+ if (!this_channel->copy)
+ {
+ this_channel->out_filter = FILTER_ACCEPT;
+ this_channel->preference = ($1 == NET_ETH) ?
+ DEF_PREF_L3VPN_IMPORT :
+ DEF_PREF_L3VPN_EXPORT;
+ }
+};
+
+evpn_channel: evpn_channel_start channel_opt_list channel_end;
+
+evpn_proto_start: proto_start EVPN
+{
+ this_proto = proto_config_new(&proto_evpn, $1);
+};
+
+
+evpn_proto_item:
+ proto_item
+ | evpn_channel
+ | mpls_channel
+ | RD VPN_RD { EVPN_CFG->rd = rd_to_u64($2); }
+ | ROUTE DISTINGUISHER VPN_RD { EVPN_CFG->rd = rd_to_u64($3); }
+ | IMPORT TARGET evpn_targets { EVPN_CFG->import_target = $3; }
+ | EXPORT TARGET evpn_targets { EVPN_CFG->export_target = $3; }
+ | ROUTE TARGET evpn_targets { EVPN_CFG->import_target =
EVPN_CFG->export_target = $3; }
+ | TUNNEL DEVICE text { EVPN_CFG->tunnel_dev = if_get_by_name($3); }
+ | ROUTER ADDRESS ipa { EVPN_CFG->router_addr = $3; }
+ | VNI expr { EVPN_CFG->vni = $2; }
+ | VID expr { EVPN_CFG->vid = $2; if ($2 > 4095) cf_error("VID must be in
range 0-4095"); }
+ ;
+
+evpn_proto_opts:
+ /* empty */
+ | evpn_proto_opts evpn_proto_item ';'
+ ;
+
+evpn_proto:
+ evpn_proto_start proto_name '{' evpn_proto_opts '}';
+
+
+evpn_targets:
+ ec_item { f_tree_only_rt($1); $$ = $1; }
+ | '[' ec_items ']' { f_tree_only_rt($2); $$ = build_tree($2, true); }
+ ;
+
+
+CF_CODE
+
+CF_END
diff --git a/proto/evpn/evpn.c b/proto/evpn/evpn.c
new file mode 100644
index 00000000..1c8cad92
--- /dev/null
+++ b/proto/evpn/evpn.c
@@ -0,0 +1,572 @@
+/*
+ * BIRD -- BGP/MPLS Ethernet Virtual Private Networks (EVPN)
+ *
+ * (c) 2023 Ondrej Zajicek <[email protected]>
+ * (c) 2023 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: BGP/MPLS Ethernet Virtual Private Networks (EVPN)
+ *
+ * The EVPN protocol implements RFC 7432 BGP Etherent VPNs using VXLAN
overlays.
+ * It works similarly to L3VPN. It connects ethernet table (one per VRF) with
+ * (global) EVPN table. Routes passed from EVPN table to ethernet table are
+ * stripped of RD and filtered by import targets, routes passed in the other
+ * direction are extended with RD, MPLS/VNI labels, and export targets in
+ * extended communities.
+ *
+ * The EVPN protocol supports MAC (type 2) and IMET (type 3) EVPN routes, there
+ * is no support for EAD / ES routes, or routes with non-zero tag. There is
also
+ * no support for MPLS backbone, just VXLAN overlays.
+ *
+ * Supported standards:
+ * RFC 7432 - BGP MPLS-Based Ethernet VPN
+ * RFC 8365 - Network Virtualization Using Ethernet VPN
+ */
+
+/*
+ * TODO:
+ * - Encapsulation community handling
+ * - MAC mobility community handling
+ * - Review preference handling
+ * - Wait for existence (and active state) of the tunnel device
+ * - Learn VNI / router address from the tunnel device
+ * - Improved VLAN handling
+ * - MPLS encapsulation mode
+ */
+
+#undef LOCAL_DEBUG
+
+#include "nest/bird.h"
+#include "nest/iface.h"
+#include "nest/protocol.h"
+#include "nest/route.h"
+#include "nest/mpls.h"
+#include "nest/cli.h"
+#include "conf/conf.h"
+#include "filter/filter.h"
+#include "filter/data.h"
+#include "lib/string.h"
+
+#include "evpn.h"
+
+#include "proto/bgp/bgp.h"
+
+#define EA_BGP_NEXT_HOP EA_CODE(PROTOCOL_BGP, BA_NEXT_HOP)
+#define EA_BGP_EXT_COMMUNITY EA_CODE(PROTOCOL_BGP, BA_EXT_COMMUNITY)
+#define EA_BGP_PMSI_TUNNEL EA_CODE(PROTOCOL_BGP, BA_PMSI_TUNNEL)
+#define EA_BGP_MPLS_LABEL_STACK EA_CODE(PROTOCOL_BGP,
BA_MPLS_LABEL_STACK)
+
+static inline const struct adata * ea_get_adata(ea_list *e, uint id)
+{ eattr *a = ea_find(e, id); return a ? a->u.ptr : &null_adata; }
+
+static inline int
+mpls_valid_nexthop(const rta *a)
+{
+ /* MPLS does not support special blackhole targets */
+ if (a->dest != RTD_UNICAST)
+ return 0;
+
+ /* MPLS does not support ARP / neighbor discovery */
+ for (const struct nexthop *nh = &a->nh; nh ; nh = nh->next)
+ if (ipa_zero(nh->gw) && (nh->iface->flags & IF_MULTIACCESS))
+ return 0;
+
+ return 1;
+}
+
+static int
+evpn_import_targets(struct evpn_proto *p, const struct adata *list)
+{
+ return (p->import_target_one) ?
+ ec_set_contains(list, p->import_target->from.val.ec) :
+ eclist_match_set(list, p->import_target);
+}
+
+static struct adata *
+evpn_export_targets(struct evpn_proto *p, const struct adata *src)
+{
+ u32 *s = int_set_get_data(src);
+ int len = int_set_get_size(src);
+
+ struct adata *dst = lp_alloc(tmp_linpool, sizeof(struct adata) + (len +
p->export_target_length) * sizeof(u32));
+ u32 *d = int_set_get_data(dst);
+ int end = 0;
+
+ for (int i = 0; i < len; i += 2)
+ {
+ /* Remove existing route targets */
+ uint type = s[i] >> 16;
+ if (ec_type_is_rt(type))
+ continue;
+
+ d[end++] = s[i];
+ d[end++] = s[i+1];
+ }
+
+ /* Add new route targets */
+ memcpy(d + end, p->export_target_data, p->export_target_length *
sizeof(u32));
+ end += p->export_target_length;
+
+ /* Set length */
+ dst->length = end * sizeof(u32);
+
+ return dst;
+}
+
+static inline void
+evpn_prepare_import_targets(struct evpn_proto *p)
+{
+ const struct f_tree *t = p->import_target;
+ p->import_target_one = !t->left && !t->right && (t->from.val.ec ==
t->to.val.ec);
+}
+
+static void
+evpn_add_ec(const struct f_tree *t, void *P)
+{
+ struct evpn_proto *p = P;
+ ec_put(p->export_target_data, p->export_target_length, t->from.val.ec);
+ p->export_target_length += 2;
+}
+
+static void
+evpn_prepare_export_targets(struct evpn_proto *p)
+{
+ if (p->export_target_data)
+ mb_free(p->export_target_data);
+
+ uint len = 2 * tree_node_count(p->export_target);
+ p->export_target_data = mb_alloc(p->p.pool, len * sizeof(u32));
+ p->export_target_length = 0;
+ tree_walk(p->export_target, evpn_add_ec, p);
+ ASSERT(p->export_target_length == len);
+}
+
+static void
+evpn_announce_mac(struct evpn_proto *p, const net_addr_eth *n0, rte *new)
+{
+ struct channel *c = p->evpn_channel;
+
+ net_addr *n = alloca(sizeof(net_addr_evpn_mac));
+ net_fill_evpn_mac(n, p->rd, 0, n0->mac);
+
+ if (new)
+ {
+ rta *a = alloca(RTA_MAX_SIZE);
+ *a = (rta) {
+ .source = RTS_EVPN,
+ .scope = SCOPE_UNIVERSE,
+ .pref = c->preference,
+ };
+
+ struct adata *ad = evpn_export_targets(p, &null_adata);
+ ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_EXT_COMMUNITY, 0,
EAF_TYPE_EC_SET, ad);
+
+ ea_set_attr_u32(&a->eattrs, tmp_linpool, EA_MPLS_LABEL, 0, EAF_TYPE_INT,
p->vni);
+
+ rte *e = rte_get_temp(a, p->p.main_source);
+ rte_update2(c, n, e, p->p.main_source);
+ }
+ else
+ {
+ rte_update2(c, n, NULL, p->p.main_source);
+ }
+}
+
+static void
+evpn_announce_imet(struct evpn_proto *p, int new)
+{
+ struct channel *c = p->evpn_channel;
+
+ net_addr *n = alloca(sizeof(net_addr_evpn_imet));
+ net_fill_evpn_imet(n, p->rd, 0, p->router_addr);
+
+ if (new)
+ {
+ rta *a = alloca(RTA_MAX_SIZE);
+ *a = (rta) {
+ .source = RTS_EVPN,
+ .scope = SCOPE_UNIVERSE,
+ .pref = c->preference,
+ };
+
+ struct adata *ad = evpn_export_targets(p, &null_adata);
+ ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_EXT_COMMUNITY, 0,
EAF_TYPE_EC_SET, ad);
+
+ ad = bgp_pmsi_new_ingress_replication(tmp_linpool, p->router_addr, p->vni);
+ ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_PMSI_TUNNEL, 0,
EAF_TYPE_OPAQUE, ad);
+
+ rte *e = rte_get_temp(a, p->p.main_source);
+ rte_update2(c, n, e, p->p.main_source);
+ }
+ else
+ {
+ rte_update2(c, n, NULL, p->p.main_source);
+ }
+}
+
+#define BAD(msg, args...) \
+ ({ log(L_ERR "%s: " msg, p->p.name, ## args); goto withdraw; })
+
+
+static void
+evpn_receive_mac(struct evpn_proto *p, const net_addr_evpn_mac *n0, rte *new)
+{
+ struct channel *c = p->eth_channel;
+
+ net_addr *n = alloca(sizeof(net_addr_eth));
+ net_fill_eth(n, n0->mac, p->vid);
+
+ if (new && rte_resolvable(new))
+ {
+ eattr *nh = ea_find(new->attrs->eattrs, EA_BGP_NEXT_HOP);
+ if (!nh)
+ BAD("Missing NEXT_HOP attribute in %N", n0);
+
+ eattr *ms = ea_find(new->attrs->eattrs, EA_BGP_MPLS_LABEL_STACK);
+ if (!ms)
+ BAD("Missing MPLS label stack in %N", n0);
+
+ rta *a = alloca(RTA_MAX_SIZE);
+ *a = (rta) {
+ .source = RTS_EVPN,
+ .scope = SCOPE_UNIVERSE,
+ .dest = RTD_UNICAST,
+ .pref = c->preference,
+ .nh.gw = *((ip_addr *) nh->u.ptr->data),
+ .nh.iface = p->tunnel_dev,
+ };
+
+ a->nh.labels = MIN(ms->u.ptr->length / 4, MPLS_MAX_LABEL_STACK);
+ memcpy(a->nh.label, ms->u.ptr->data, a->nh.labels * 4);
+
+ rte *e = rte_get_temp(a, p->p.main_source);
+ rte_update2(c, n, e, p->p.main_source);
+ }
+ else
+ {
+ withdraw:
+ rte_update2(c, n, NULL, p->p.main_source);
+ }
+}
+
+static void
+evpn_receive_imet(struct evpn_proto *p, const net_addr_evpn_imet *n0, rte *new)
+{
+ struct channel *c = p->eth_channel;
+ struct rte_src *s = rt_get_source(&p->p, n0->rd);
+
+ net_addr *n = alloca(sizeof(net_addr_eth));
+ net_fill_eth(n, MAC_NONE, p->vid);
+
+ if (new && rte_resolvable(new))
+ {
+ eattr *pt = ea_find(new->attrs->eattrs, EA_BGP_PMSI_TUNNEL);
+ if (!pt)
+ BAD("Missing PMSI_TUNNEL attribute in %N", n0);
+
+ uint pmsi_type = bgp_pmsi_get_type(pt->u.ptr);
+ if (pmsi_type != BGP_PMSI_TYPE_INGRESS_REPLICATION)
+ BAD("Unsupported PMSI_TUNNEL type %u in %N", pmsi_type, n0);
+
+ rta *a = alloca(RTA_MAX_SIZE);
+ *a = (rta) {
+ .source = RTS_EVPN,
+ .scope = SCOPE_UNIVERSE,
+ .dest = RTD_UNICAST,
+ .pref = c->preference,
+ .nh.gw = bgp_pmsi_ir_get_endpoint(pt->u.ptr),
+ .nh.iface = p->tunnel_dev,
+ };
+
+ a->nh.labels = 1;
+ a->nh.label[0] = bgp_pmsi_get_label(pt->u.ptr);
+
+ rte *e = rte_get_temp(a, s);
+ rte_update2(c, n, e, s);
+ }
+ else
+ {
+ withdraw:
+ rte_update2(c, n, NULL, s);
+ }
+}
+
+
+
+static void
+evpn_rt_notify(struct proto *P, struct channel *c0 UNUSED, net *net, rte *new,
rte *old UNUSED)
+{
+ struct evpn_proto *p = (void *) P;
+ const net_addr *n = net->n.addr;
+
+ switch (n->type)
+ {
+ case NET_ETH:
+ evpn_announce_mac(p, (const net_addr_eth *) n, new);
+ return;
+
+ case NET_EVPN:
+ switch (((const net_addr_evpn *) n)->subtype)
+ {
+ case NET_EVPN_MAC:
+ evpn_receive_mac(p, (const net_addr_evpn_mac *) n, new);
+ return;
+
+ case NET_EVPN_IMET:
+ evpn_receive_imet(p, (const net_addr_evpn_imet *) n, new);;
+ return;
+ }
+ return;
+
+ case NET_MPLS:
+ return;
+ }
+}
+
+
+static int
+evpn_preexport(struct channel *C, rte *e)
+{
+ struct evpn_proto *p = (void *) C->proto;
+ struct proto *pp = e->sender->proto;
+ const net_addr *n = e->net->n.addr;
+
+ if (pp == C->proto)
+ return -1; /* Avoid local loops automatically */
+
+ switch (n->type)
+ {
+ case NET_ETH:
+ if (((const net_addr_eth *) n)->vid != p->vid)
+ return -1;
+
+ return 0;
+
+ case NET_EVPN:
+ return evpn_import_targets(p, ea_get_adata(e->attrs->eattrs,
EA_BGP_EXT_COMMUNITY)) ? 0 : -1;
+
+ case NET_MPLS:
+ return -1;
+
+ default:
+ bug("invalid type");
+ }
+}
+
+static void
+evpn_reload_routes(struct channel *C)
+{
+ struct evpn_proto *p = (void *) C->proto;
+
+ /* Route reload on one channel is just refeed on the other */
+ switch (C->net_type)
+ {
+ case NET_ETH:
+ channel_request_feeding(p->evpn_channel);
+ break;
+
+ case NET_EVPN:
+ channel_request_feeding(p->eth_channel);
+ break;
+
+ case NET_MPLS:
+ channel_request_feeding(p->eth_channel);
+ break;
+ }
+}
+
+static inline u32
+evpn_metric(rte *e)
+{
+ u32 metric = ea_get_int(e->attrs->eattrs, EA_GEN_IGP_METRIC,
e->attrs->igp_metric);
+ return MIN(metric, IGP_METRIC_UNKNOWN);
+}
+
+static int
+evpn_rte_better(rte *new, rte *old)
+{
+ /* This is hack, we should have full BGP-style comparison */
+ return evpn_metric(new) < evpn_metric(old);
+}
+
+static void
+evpn_postconfig(struct proto_config *CF)
+{
+ struct evpn_config *cf = (void *) CF;
+
+ if (!proto_cf_find_channel(CF, NET_ETH))
+ cf_error("Ethernet channel not specified");
+
+ if (!proto_cf_find_channel(CF, NET_EVPN))
+ cf_error("EVPN channel not specified");
+
+// if (!proto_cf_find_channel(CF, NET_MPLS))
+// cf_error("MPLS channel not specified");
+
+ if (!cf->rd)
+ cf_error("Route distinguisher not specified");
+
+ if (!cf->import_target && !cf->export_target)
+ cf_error("Route target not specified");
+
+ if (!cf->import_target)
+ cf_error("Import target not specified");
+
+ if (!cf->export_target)
+ cf_error("Export target not specified");
+}
+
+static struct proto *
+evpn_init(struct proto_config *CF)
+{
+ struct proto *P = proto_new(CF);
+ struct evpn_proto *p = (void *) P;
+ // struct evpn_config *cf = (void *) CF;
+
+ proto_configure_channel(P, &p->eth_channel, proto_cf_find_channel(CF,
NET_ETH));
+ proto_configure_channel(P, &p->evpn_channel, proto_cf_find_channel(CF,
NET_EVPN));
+ proto_configure_channel(P, &P->mpls_channel, proto_cf_find_channel(CF,
NET_MPLS));
+
+ P->rt_notify = evpn_rt_notify;
+ P->preexport = evpn_preexport;
+ P->reload_routes = evpn_reload_routes;
+ P->rte_better = evpn_rte_better;
+
+ return P;
+}
+
+static int
+evpn_start(struct proto *P)
+{
+ struct evpn_proto *p = (void *) P;
+ struct evpn_config *cf = (void *) P->cf;
+
+ p->rd = cf->rd;
+ p->import_target = cf->import_target;
+ p->export_target = cf->export_target;
+ p->export_target_data = NULL;
+
+ p->tunnel_dev = cf->tunnel_dev;
+ p->router_addr = cf->router_addr;
+ p->vni = cf->vni;
+ p->vid = cf->vid;
+
+ evpn_prepare_import_targets(p);
+ evpn_prepare_export_targets(p);
+
+ proto_setup_mpls_map(P, RTS_EVPN, 1);
+
+ // XXX ?
+ if (P->vrf_set)
+ P->mpls_map->vrf_iface = P->vrf;
+
+ proto_notify_state(P, PS_UP);
+
+ evpn_announce_imet(p, 1);
+
+ return PS_UP;
+}
+
+static int
+evpn_shutdown(struct proto *P)
+{
+ // struct evpn_proto *p = (void *) P;
+
+ proto_shutdown_mpls_map(P, 1);
+
+ return PS_DOWN;
+}
+
+static int
+evpn_reconfigure(struct proto *P, struct proto_config *CF)
+{
+ struct evpn_proto *p = (void *) P;
+ struct evpn_config *cf = (void *) CF;
+
+ if (!proto_configure_channel(P, &p->eth_channel, proto_cf_find_channel(CF,
NET_ETH)) ||
+ !proto_configure_channel(P, &p->evpn_channel, proto_cf_find_channel(CF,
NET_EVPN)) ||
+ !proto_configure_channel(P, &P->mpls_channel, proto_cf_find_channel(CF,
NET_MPLS)))
+ return 0;
+
+ if ((p->rd != cf->rd) ||
+ (p->tunnel_dev != cf->tunnel_dev) ||
+ (!ipa_equal(p->router_addr, cf->router_addr)) ||
+ (p->vni != cf->vni) ||
+ (p->vid != cf->vid))
+ return 0;
+
+ int import_changed = !same_tree(p->import_target, cf->import_target);
+ int export_changed = !same_tree(p->export_target, cf->export_target);
+
+ /* Update pointers to config structures */
+ p->import_target = cf->import_target;
+ p->export_target = cf->export_target;
+
+ proto_setup_mpls_map(P, RTS_EVPN, 1);
+
+ if (import_changed)
+ {
+ TRACE(D_EVENTS, "Import target changed");
+
+ evpn_prepare_import_targets(p);
+
+ if (p->evpn_channel && (p->evpn_channel->channel_state == CS_UP))
+ channel_request_feeding(p->evpn_channel);
+ }
+
+ if (export_changed)
+ {
+ TRACE(D_EVENTS, "Export target changed");
+
+ evpn_prepare_export_targets(p);
+
+ if (p->eth_channel && (p->eth_channel->channel_state == CS_UP))
+ channel_request_feeding(p->eth_channel);
+ }
+
+ return 1;
+}
+
+static void
+evpn_copy_config(struct proto_config *dest UNUSED, struct proto_config *src
UNUSED)
+{
+ /* Just a shallow copy, not many items here */
+}
+
+/*
+static void
+evpn_get_route_info(rte *rte, byte *buf)
+{
+ u32 metric = evpn_metric(rte);
+ if (metric < IGP_METRIC_UNKNOWN)
+ bsprintf(buf, " (%u/%u)", rte->attrs->pref, metric);
+ else
+ bsprintf(buf, " (%u/?)", rte->attrs->pref);
+}
+*/
+
+
+struct protocol proto_evpn = {
+ .name = "EVPN",
+ .template = "evpn%d",
+ .class = PROTOCOL_EVPN,
+ .channel_mask = NB_ETH | NB_EVPN | NB_MPLS,
+ .proto_size = sizeof(struct evpn_proto),
+ .config_size = sizeof(struct evpn_config),
+ .postconfig = evpn_postconfig,
+ .init = evpn_init,
+ .start = evpn_start,
+ .shutdown = evpn_shutdown,
+ .reconfigure = evpn_reconfigure,
+ .copy_config = evpn_copy_config,
+// .get_route_info = evpn_get_route_info
+};
+
+void
+evpn_build(void)
+{
+ proto_build(&proto_evpn);
+}
diff --git a/proto/evpn/evpn.h b/proto/evpn/evpn.h
new file mode 100644
index 00000000..e511cd0f
--- /dev/null
+++ b/proto/evpn/evpn.h
@@ -0,0 +1,44 @@
+/*
+ * BIRD -- BGP/MPLS Ethernet Virtual Private Networks (EVPN)
+ *
+ * (c) 2023 Ondrej Zajicek <[email protected]>
+ * (c) 2023 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_EVPN_H_
+#define _BIRD_EVPN_H_
+
+struct evpn_config {
+ struct proto_config c;
+
+ u64 rd;
+ struct f_tree *import_target;
+ struct f_tree *export_target;
+
+ struct iface *tunnel_dev;
+ ip_addr router_addr;
+ u32 vni;
+ u32 vid;
+};
+
+struct evpn_proto {
+ struct proto p;
+ struct channel *eth_channel;
+ struct channel *evpn_channel;
+
+ u64 rd;
+ struct f_tree *import_target;
+ struct f_tree *export_target;
+ u32 *export_target_data;
+ uint export_target_length;
+ uint import_target_one;
+
+ struct iface *tunnel_dev;
+ ip_addr router_addr;
+ u32 vni;
+ u32 vid;
+};
+
+#endif
diff --git a/sysdep/linux/netlink.c b/sysdep/linux/netlink.c
index 8be5112c..55f45a73 100644
--- a/sysdep/linux/netlink.c
+++ b/sysdep/linux/netlink.c
@@ -27,6 +27,8 @@
#include "lib/hash.h"
#include "conf/conf.h"
+#include "proto/bridge/bridge.h"
+
#include CONFIG_INCLUDE_NLSYS_H
#define krt_ipv4(p) ((p)->af == AF_INET)
@@ -40,6 +42,7 @@ struct nl_parse_state
int scan;
u32 rta_flow;
+ u32 bridge_id;
};
/*
@@ -210,6 +213,34 @@ nl_request_dump_route(int af, int table_id)
nl_scan.last_hdr = NULL;
}
+static void
+nl_request_dump_neigh(int af, int bridge_id)
+{
+ struct {
+ struct nlmsghdr nh;
+ struct ndmsg ndm;
+ struct rtattr rta;
+ u32 master_id;
+ } req = {
+ .nh.nlmsg_type = RTM_GETNEIGH,
+ .nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)),
+ .nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
+ .nh.nlmsg_seq = ++(nl_scan.seq),
+ .ndm.ndm_family = af,
+ };
+
+ if (bridge_id)
+ {
+ req.rta.rta_type = NDA_MASTER;
+ req.rta.rta_len = RTA_LENGTH(4);
+ req.master_id = bridge_id;
+ req.nh.nlmsg_len = NLMSG_ALIGN(req.nh.nlmsg_len) + req.rta.rta_len;
+ }
+
+ send(nl_scan.fd, &req, req.nh.nlmsg_len, 0);
+ nl_scan.last_hdr = NULL;
+}
+
static struct nlmsghdr *
nl_get_reply(struct nl_sock *nl)
@@ -444,6 +475,16 @@ static struct nl_want_attrs
rtm_attr_want_mpls[BIRD_RTA_MAX] = {
#endif
+#define BIRD_NDA_MAX (NDA_MASTER+1)
+
+static struct nl_want_attrs ndm_attr_want[BIRD_NDA_MAX] = {
+ [NDA_LLADDR] = { 1, 1, sizeof(mac_addr) },
+ [NDA_VLAN] = { 1, 1, sizeof(u16) },
+ [NDA_VNI] = { 1, 1, sizeof(u32) },
+ [NDA_MASTER] = { 1, 1, sizeof(u32) },
+};
+
+
static int
nl_parse_attrs(struct rtattr *a, struct nl_want_attrs *want, struct rtattr
**k, int ksize)
{
@@ -496,6 +537,9 @@ static inline ip_addr rta_get_ipa(struct rtattr *a)
return ipa_from_ip6(rta_get_ip6(a));
}
+static inline mac_addr rta_get_mac(struct rtattr *a)
+{ return *(mac_addr *) RTA_DATA(a); }
+
static inline ip_addr rta_get_via(struct rtattr *a)
{
struct rtvia *v = RTA_DATA(a);
@@ -1281,7 +1325,7 @@ static HASH(struct krt_proto) nl_table_map;
#define RTH_FN(a,i) a ^ u32_hash(i)
#define RTH_REHASH rth_rehash
-#define RTH_PARAMS /8, *2, 2, 2, 6, 20
+#define RTH_PARAMS /8, *1, 2, 2, 4, 20
HASH_DEFINE_REHASH_FN(RTH, struct krt_proto)
@@ -1939,6 +1983,207 @@ krt_do_scan(struct krt_proto *p)
}
}
+
+/*
+ * FDB entries
+ */
+
+static inline u32
+kbr_bridge_id(struct kbr_proto *p)
+{
+ return p->bridge_dev->index;
+}
+
+static HASH(struct kbr_proto) nl_bridge_map;
+
+#define BRH_KEY(p) kbr_bridge_id(p)
+#define BRH_NEXT(p) p->hash_next
+#define BRH_EQ(i1,i2) i1 == i2
+#define BRH_FN(i) u32_hash(i)
+
+#define BRH_REHASH brh_rehash
+#define BRH_PARAMS /8, *1, 2, 2, 4, 20
+
+HASH_DEFINE_REHASH_FN(BRH, struct kbr_proto);
+
+static int
+nl_send_fdb(const net_addr *n0, rte *e, int op, int tunnel)
+{
+ const net_addr_eth *n = (void *) n0;
+
+ struct {
+ struct nlmsghdr h;
+ struct ndmsg n;
+ char buf[0];
+ } *r;
+
+ int rsize = sizeof(*r) + 256;
+ r = alloca(rsize);
+
+ memset(&r->h, 0, sizeof(r->h));
+ memset(&r->n, 0, sizeof(r->n));
+ r->h.nlmsg_type = op ? RTM_NEWNEIGH : RTM_DELNEIGH;
+ r->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg));
+ r->h.nlmsg_flags = op | NLM_F_REQUEST | NLM_F_ACK;
+
+ r->n.ndm_family = AF_BRIDGE;
+ r->n.ndm_state = NUD_NOARP;
+ r->n.ndm_flags = (tunnel ? NTF_SELF : NTF_MASTER) | NTF_EXT_LEARNED;
+
+ nl_add_attr(&r->h, rsize, NDA_LLADDR, n->mac.addr, 6);
+
+ if (n->vid)
+ nl_add_attr_u16(&r->h, rsize, NDA_VLAN, n->vid);
+
+ struct nexthop *nh = &e->attrs->nh;
+ ASSERT(e->attrs->dest == RTD_UNICAST && !nh->next);
+ r->n.ndm_ifindex = nh->iface->index;
+
+ if (tunnel)
+ {
+ ASSERT(ipa_nonzero(nh->gw));
+ nl_add_attr_ipa(&r->h, rsize, NDA_DST, nh->gw);
+
+ if (nh->labels)
+ nl_add_attr_u32(&r->h, rsize, NDA_VNI, nh->label[0]);
+
+ r->n.ndm_state |= NUD_PERMANENT;
+ }
+
+ /* Ignore missing for DELETE */
+ return nl_exchange(&r->h, (op == NL_OP_DELETE));
+}
+
+void
+kbr_replace_fdb(const net_addr *n, rte *new, rte *old, int tunnel)
+{
+ int err = 0;
+
+ if (old && new)
+ {
+ err = nl_send_fdb(n, new, NL_OP_REPLACE, tunnel);
+ }
+ else
+ {
+ if (old)
+ nl_send_fdb(n, old, NL_OP_DELETE, tunnel);
+
+ if (new)
+ err = nl_send_fdb(n, new, NL_OP_ADD, tunnel);
+ }
+
+ if (err < 0)
+ log(L_WARN "NL error %m");
+}
+
+void
+kbr_update_fdb(const net_addr *n, rte *new, rte *old, int tunnel)
+{
+ int err = 0;
+
+ if (old)
+ nl_send_fdb(n, old, NL_OP_DELETE, tunnel);
+
+ if (new)
+ err = nl_send_fdb(n, new, NL_OP_APPEND, tunnel);
+
+ if (err < 0)
+ log(L_WARN "NL error %m");
+}
+
+#ifndef NDM_RTA
+#define NDM_RTA(n) (struct rtattr *)(((char *) n) + NLMSG_ALIGN(sizeof(struct
ndmsg)))
+#endif
+
+static void
+nl_parse_fdb(struct nl_parse_state *s, struct nlmsghdr *h)
+{
+ struct ndmsg *nd;
+ struct rtattr *a[BIRD_NDA_MAX];
+ int new = (h->nlmsg_type == RTM_NEWNEIGH);
+
+ if (!(nd = nl_checkin(h, sizeof(*nd))))
+ return;
+
+ if (nd->ndm_family != AF_BRIDGE)
+ return;
+
+ if (!nl_parse_attrs(NDM_RTA(nd), ndm_attr_want, a, sizeof(a)))
+ return;
+
+ if (!a[NDA_LLADDR] || !a[NDA_MASTER])
+ return;
+
+ uint vid = a[NDA_VLAN] ? rta_get_u16(a[NDA_VLAN]) : 0;
+
+ net_addr n;
+ net_fill_eth(&n, rta_get_mac(a[NDA_LLADDR]), vid);
+
+ u32 bridge_id = rta_get_u32(a[NDA_MASTER]);
+
+ /* Should be filtered by kernel */
+ if (s->bridge_id && (bridge_id != s->bridge_id))
+ return;
+
+ /* Do we know this bridge? */
+ struct kbr_proto *p = HASH_FIND(nl_bridge_map, BRH, bridge_id);
+ if (!p)
+ return;
+
+ /* Accept VLAN-tagged entries when vlan filtering is enabled */
+ if (!vid != !p->vlan_filtering)
+ return;
+
+ struct iface *oif = if_find_by_index(nd->ndm_ifindex);
+ if (!oif)
+ return;
+
+ rta ra = {
+ .source = RTS_BRIDGE,
+ .scope = SCOPE_UNIVERSE,
+ .dest = RTD_UNICAST,
+ .nh.iface = oif,
+ };
+
+ int src;
+ if (nd->ndm_flags & NTF_EXT_LEARNED)
+ // src = KBR_SRC_BIRD;
+ return;
+ else if (nd->ndm_state & NUD_PERMANENT)
+ src = KBR_SRC_LOCAL;
+ else if (nd->ndm_state & NUD_NOARP)
+ src = KBR_SRC_STATIC;
+ else
+ src = KBR_SRC_DYNAMIC;
+
+ ea_set_attr_u32(&ra.eattrs, s->pool, EA_KBR_SOURCE, 0, EAF_TYPE_INT, src);
+
+ rte *e = new ? rte_get_temp(&ra, p->p.main_source) : NULL;
+
+ DBG("FDB %s %N dev %s [%x %x %x]\n", (new ? "add" : "del"), &n, oif->name,
nd->ndm_state, nd->ndm_flags, nd->ndm_type);
+ kbr_got_route(p, &n, e, src, s->scan);
+}
+
+void
+kbr_do_scan(struct kbr_proto *p)
+{
+ struct nl_parse_state s = {
+ .pool = nl_linpool,
+ .scan = 1,
+ .bridge_id = kbr_bridge_id(p),
+ };
+
+ nl_request_dump_neigh(AF_BRIDGE, kbr_bridge_id(p));
+
+ struct nlmsghdr *h;
+ while (h = nl_get_scan())
+ {
+ if (h->nlmsg_type == RTM_NEWNEIGH || h->nlmsg_type == RTM_DELNEIGH)
+ nl_parse_fdb(&s, h);
+ }
+}
+
+
/*
* Asynchronous Netlink interface
*/
@@ -1964,18 +2209,25 @@ nl_async_msg(struct nlmsghdr *h)
DBG("KRT: Received async route notification (%d)\n", h->nlmsg_type);
nl_parse_route(&s, h);
break;
+
case RTM_NEWLINK:
case RTM_DELLINK:
DBG("KRT: Received async link notification (%d)\n", h->nlmsg_type);
if (kif_proto)
nl_parse_link(h, 0);
break;
+
case RTM_NEWADDR:
case RTM_DELADDR:
DBG("KRT: Received async address notification (%d)\n", h->nlmsg_type);
if (kif_proto)
nl_parse_addr(h, 0);
break;
+
+ case RTM_NEWNEIGH:
+ case RTM_DELNEIGH:
+ nl_parse_fdb(&s, h);
+
default:
DBG("KRT: Received unknown async notification (%d)\n", h->nlmsg_type);
}
@@ -2062,7 +2314,7 @@ nl_open_async(void)
bzero(&sa, sizeof(sa));
sa.nl_family = AF_NETLINK;
- sa.nl_groups = RTMGRP_LINK |
+ sa.nl_groups = RTMGRP_LINK | RTMGRP_NEIGH |
RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE |
RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
@@ -2119,7 +2371,8 @@ void
krt_sys_io_init(void)
{
nl_linpool = lp_new_default(krt_pool);
- HASH_INIT(nl_table_map, krt_pool, 6);
+ HASH_INIT(nl_table_map, krt_pool, 4);
+ HASH_INIT(nl_bridge_map, krt_pool, 4);
}
int
@@ -2223,6 +2476,32 @@ krt_sys_get_attr(const eattr *a, byte *buf, int buflen
UNUSED)
}
+int
+kbr_sys_start(struct kbr_proto *p)
+{
+ struct kbr_proto *old = HASH_FIND(nl_bridge_map, BRH, kbr_bridge_id(p));
+
+ if (old)
+ {
+ log(L_ERR "%s: Bridge device %s already registered by %s",
+ p->p.name, p->bridge_dev->name, old->p.name);
+ return 0;
+ }
+
+ HASH_INSERT2(nl_bridge_map, BRH, krt_pool, p);
+
+ nl_open();
+ nl_open_async();
+
+ return 1;
+}
+
+void
+kbr_sys_shutdown(struct kbr_proto *p)
+{
+ HASH_REMOVE2(nl_bridge_map, BRH, krt_pool, p);
+}
+
void
kif_sys_start(struct kif_proto *p UNUSED)