Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ ForEachMacros:
- gr_api_client_stream_foreach
- vec_foreach
- vec_foreach_ref
- gr_nh_flags_foreach
- gr_flags_foreach
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Regroup
Expand Down
5 changes: 5 additions & 0 deletions api/gr_macro.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <errno.h>
#include <limits.h>
#include <stdint.h>

// Get number of elements in a static array.
#define ARRAY_DIM(array) (sizeof(array) / sizeof(array[0]))
Expand Down Expand Up @@ -48,3 +49,7 @@

#define GR_SYMBOL_FORBIDDEN(func, new_func) \
sorry_##func##_is_a_banned_function_use_##new_func##_instead

#define gr_flags_foreach(f, flags) \
for (uint64_t __bit = 0, f = 1ULL; __bit < sizeof(flags) * CHAR_BIT; f = 1ULL << ++__bit) \
if (flags & f)
16 changes: 16 additions & 0 deletions frr/if_grout.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "if_grout.h"
#include "if_map.h"
#include "l3vni_map.h"
#include "log_grout.h"

#include <gr_ip4.h>
Expand Down Expand Up @@ -48,6 +49,7 @@ void grout_link_change(struct gr_iface *gr_if, bool new, bool startup) {
const struct gr_iface_info_vlan *gr_vlan = NULL;
const struct gr_iface_info_port *gr_port = NULL;
const struct gr_iface_info_bond *gr_bond = NULL;
const struct gr_iface_info_vrf *gr_vrf = NULL;
ifindex_t bridge_ifindex = IFINDEX_INTERNAL;
ifindex_t link_ifindex = IFINDEX_INTERNAL;
ifindex_t bond_ifindex = IFINDEX_INTERNAL;
Expand Down Expand Up @@ -83,6 +85,8 @@ void grout_link_change(struct gr_iface *gr_if, bool new, bool startup) {
link_type = ZEBRA_LLT_IPIP;
break;
case GR_IFACE_TYPE_VRF:
gr_vrf = (const struct gr_iface_info_vrf *)&gr_if->info;
mac = &gr_vrf->mac;
link_type = ZEBRA_LLT_ETHER;
zif_type = ZEBRA_IF_VRF;
break;
Expand Down Expand Up @@ -133,6 +137,16 @@ void grout_link_change(struct gr_iface *gr_if, bool new, bool startup) {
dplane_ctx_set_ifp_table_id(
ctx, vrf_grout_to_frr(gr_if->base.vrf_id)
);

// For VXLAN in VRF mode, present it as a bridge slave
// of the VRF interface. FRR requires an SVI (derived
// from the bridge master) to bring the L3VNI up and
// compute the Router MAC for EVPN type-5 routes.
if (zif_type == ZEBRA_IF_VXLAN) {
bridge_ifindex = ifindex_grout_to_frr(gr_if->base.vrf_id);
slave_type = ZEBRA_IF_SLAVE_BRIDGE;
l3vni_set(gr_if->base.vrf_id, gr_if->id);
}
break;
case GR_IFACE_MODE_BOND:
bond_ifindex = ifindex_grout_to_frr(gr_if->domain_id);
Expand Down Expand Up @@ -179,6 +193,8 @@ void grout_link_change(struct gr_iface *gr_if, bool new, bool startup) {
} else {
dplane_ctx_set_op(ctx, DPLANE_OP_INTF_DELETE);
dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_QUEUED);
if (gr_vxlan != NULL && gr_if->mode == GR_IFACE_MODE_VRF)
l3vni_del(gr_if->base.vrf_id);
remove_mapping_by_grout_ifindex(gr_if->id);
}

Expand Down
119 changes: 119 additions & 0 deletions frr/l3vni_map.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2026 Robin Jarry

#include "if_map.h"
#include "l3vni_map.h"

#include <gr_infra.h>

#include <lib/jhash.h>
#include <lib/typesafe.h>

// All functions in this file run exclusively on the dplane thread
// (grout_link_change, grout_add_nexthop, grout_neigh_update_ctx).
// No locking required.

// VRF -> VXLAN iface mapping ///////////////////////////////////////////////////

PREDECL_HASH(l3vni_hash);

struct l3vni_entry {
struct l3vni_hash_item item;
uint16_t vrf_id;
uint16_t vxlan_iface_id;
};

static int l3vni_cmp(const struct l3vni_entry *a, const struct l3vni_entry *b) {
return numcmp(a->vrf_id, b->vrf_id);
}

static uint32_t l3vni_hashfn(const struct l3vni_entry *e) {
return e->vrf_id;
}

DECLARE_HASH(l3vni_hash, struct l3vni_entry, item, l3vni_cmp, l3vni_hashfn);
static struct l3vni_hash_head l3vni_entries = INIT_HASH(l3vni_entries);

void l3vni_set(uint16_t vrf_id, uint16_t vxlan_iface_id) {
struct l3vni_entry *e, key = {.vrf_id = vrf_id};

e = l3vni_hash_find(&l3vni_entries, &key);
if (e != NULL) {
e->vxlan_iface_id = vxlan_iface_id;
return;
}
e = XCALLOC(MTYPE_GROUT_MEM, sizeof(*e));
e->vrf_id = vrf_id;
e->vxlan_iface_id = vxlan_iface_id;
l3vni_hash_add(&l3vni_entries, e);
}

void l3vni_del(uint16_t vrf_id) {
struct l3vni_entry key = {.vrf_id = vrf_id};
struct l3vni_entry *e = l3vni_hash_find(&l3vni_entries, &key);

if (e != NULL) {
l3vni_hash_del(&l3vni_entries, e);
XFREE(MTYPE_GROUT_MEM, e);
}
}

uint16_t l3vni_get_vxlan(uint16_t vrf_id) {
struct l3vni_entry key = {.vrf_id = vrf_id};
struct l3vni_entry *e = l3vni_hash_find(&l3vni_entries, &key);
return e ? e->vxlan_iface_id : GR_IFACE_ID_UNDEF;
}

// (VRF, VTEP) -> RMAC cache ///////////////////////////////////////////////////

PREDECL_HASH(rmac_hash);

struct rmac_entry {
struct rmac_hash_item item;
uint16_t vrf_id;
ip4_addr_t vtep;
struct ethaddr mac;
};

static int rmac_cmp(const struct rmac_entry *a, const struct rmac_entry *b) {
int r = numcmp(a->vrf_id, b->vrf_id);
return r ? r : numcmp(a->vtep, b->vtep);
}

static uint32_t rmac_hashfn(const struct rmac_entry *e) {
return jhash_2words(e->vrf_id, e->vtep, 0);
}

DECLARE_HASH(rmac_hash, struct rmac_entry, item, rmac_cmp, rmac_hashfn);
static struct rmac_hash_head rmac_entries = INIT_HASH(rmac_entries);

void l3vni_rmac_set(uint16_t vrf_id, ip4_addr_t vtep, const struct ethaddr *mac) {
struct rmac_entry *e, key = {.vrf_id = vrf_id, .vtep = vtep};

e = rmac_hash_find(&rmac_entries, &key);
if (e != NULL) {
e->mac = *mac;
return;
}
e = XCALLOC(MTYPE_GROUT_MEM, sizeof(*e));
e->vrf_id = vrf_id;
e->vtep = vtep;
e->mac = *mac;
rmac_hash_add(&rmac_entries, e);
}

void l3vni_rmac_del(uint16_t vrf_id, ip4_addr_t vtep) {
struct rmac_entry key = {.vrf_id = vrf_id, .vtep = vtep};
struct rmac_entry *e = rmac_hash_find(&rmac_entries, &key);

if (e != NULL) {
rmac_hash_del(&rmac_entries, e);
XFREE(MTYPE_GROUT_MEM, e);
}
}

const struct ethaddr *l3vni_rmac_get(uint16_t vrf_id, ip4_addr_t vtep) {
struct rmac_entry key = {.vrf_id = vrf_id, .vtep = vtep};
struct rmac_entry *e = rmac_hash_find(&rmac_entries, &key);
return e ? &e->mac : NULL;
}
45 changes: 45 additions & 0 deletions frr/l3vni_map.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2026 Robin Jarry

// L3VNI dplane-thread state for EVPN symmetric IRB (Integrated Routing and
// Bridging).
//
// FRR's EVPN type-5 (IP prefix) routes use a per-VRF L3 VNI with a VXLAN
// interface. Two mappings are maintained on the dplane thread (no locking):
//
// VRF -> VXLAN iface
//
// grout_add_nexthop() redirects nexthops from the VRF (FRR's SVI model) to
// the VXLAN interface so that ip_output routes packets into the tunnel.
//
// (VRF, VTEP) -> RMAC
//
// DPLANE_OP_NEIGH_INSTALL delivers the remote router MAC before
// DPLANE_OP_NH_INSTALL creates the nexthop. The RMAC is cached here and
// applied by grout_add_nexthop() when the nexthop arrives.

#pragma once

#include "lib/prefix.h"

#include <gr_net_types.h>

#include <stdint.h>

// Register vrf_id -> vxlan_iface_id mapping.
void l3vni_set(uint16_t vrf_id, uint16_t vxlan_iface_id);

// Remove mapping for vrf_id.
void l3vni_del(uint16_t vrf_id);

// Return vxlan iface id for vrf_id, or GR_IFACE_ID_UNDEF.
uint16_t l3vni_get_vxlan(uint16_t vrf_id);

// Cache remote VTEP router MAC for (vrf_id, vtep).
void l3vni_rmac_set(uint16_t vrf_id, ip4_addr_t vtep, const struct ethaddr *mac);

// Remove cached RMAC for (vrf_id, vtep).
void l3vni_rmac_del(uint16_t vrf_id, ip4_addr_t vtep);

// Look up cached RMAC for (vrf_id, vtep), or NULL.
const struct ethaddr *l3vni_rmac_get(uint16_t vrf_id, ip4_addr_t vtep);
1 change: 1 addition & 0 deletions frr/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ frr_plugin = shared_module(
files(
'if_grout.c',
'if_map.c',
'l3vni_map.c',
'rt_grout.c',
'zebra_dplane_grout.c',
),
Expand Down
Loading
Loading