Skip to content
Open
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
872 changes: 463 additions & 409 deletions docs/graph.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ custom_target(
# Individual command man pages
# The list is hardcoded since we can't run grcli during meson configuration.
grcli_commands = [
'address', 'affinity', 'conntrack', 'dnat44', 'events', 'fdb', 'graph',
'interface', 'logging', 'nexthop', 'ping', 'ping6', 'route',
'address', 'affinity', 'conntrack', 'dnat44', 'events', 'fdb', 'flood',
'graph', 'interface', 'logging', 'nexthop', 'ping', 'ping6', 'route',
'router-advert', 'snat44', 'stats', 'trace', 'traceroute', 'traceroute6',
'tunsrc',
]
Expand Down
34 changes: 30 additions & 4 deletions frr/if_grout.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@

#include <gr_ip4.h>
#include <gr_ip6.h>
#include <gr_l2.h>
#include <gr_srv6.h>

#include <linux/if.h>
#include <net/if.h>
#include <zebra/interface.h>
#include <zebra/zebra_vxlan.h>
#include <zebra_dplane_grout.h>

#define GROUT_NS NS_DEFAULT

static uint64_t gr_if_flags_to_netlink(struct gr_iface *gr_if, enum zebra_link_type link_type) {
uint64_t frr_if_flags = 0;

Expand Down Expand Up @@ -43,9 +43,12 @@ void grout_link_change(struct gr_iface *gr_if, bool new, bool startup) {
enum zebra_slave_iftype slave_type = ZEBRA_IF_SLAVE_NONE;
enum zebra_link_type link_type = ZEBRA_LLT_UNKNOWN;
enum zebra_iftype zif_type = ZEBRA_IF_OTHER;
const struct gr_iface_info_bridge *gr_bridge = NULL;
const struct gr_iface_info_vxlan *gr_vxlan = NULL;
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;
ifindex_t bridge_ifindex = IFINDEX_INTERNAL;
ifindex_t link_ifindex = IFINDEX_INTERNAL;
ifindex_t bond_ifindex = IFINDEX_INTERNAL;
const struct rte_ether_addr *mac = NULL;
Expand Down Expand Up @@ -83,6 +86,18 @@ void grout_link_change(struct gr_iface *gr_if, bool new, bool startup) {
link_type = ZEBRA_LLT_ETHER;
zif_type = ZEBRA_IF_VRF;
break;
case GR_IFACE_TYPE_BRIDGE:
gr_bridge = (const struct gr_iface_info_bridge *)&gr_if->info;
link_type = ZEBRA_LLT_ETHER;
zif_type = ZEBRA_IF_BRIDGE;
mac = &gr_bridge->mac;
break;
case GR_IFACE_TYPE_VXLAN:
gr_vxlan = (const struct gr_iface_info_vxlan *)&gr_if->info;
link_type = ZEBRA_LLT_ETHER;
zif_type = ZEBRA_IF_VXLAN;
mac = &gr_vxlan->mac;
break;
case GR_IFACE_TYPE_UNDEF:
default:
gr_log_err(
Expand Down Expand Up @@ -123,12 +138,15 @@ void grout_link_change(struct gr_iface *gr_if, bool new, bool startup) {
bond_ifindex = ifindex_grout_to_frr(gr_if->domain_id);
slave_type = ZEBRA_IF_SLAVE_BOND;
break;
case GR_IFACE_MODE_BRIDGE:
bridge_ifindex = ifindex_grout_to_frr(gr_if->domain_id);
slave_type = ZEBRA_IF_SLAVE_BRIDGE;
break;
default:
break;
}

// no bridge support in grout
dplane_ctx_set_ifp_bridge_ifindex(ctx, IFINDEX_INTERNAL);
dplane_ctx_set_ifp_bridge_ifindex(ctx, bridge_ifindex);
dplane_ctx_set_ifp_master_ifindex(ctx, IFINDEX_INTERNAL);
dplane_ctx_set_ifp_bond_ifindex(ctx, bond_ifindex);
dplane_ctx_set_ifp_zif_slave_type(ctx, slave_type);
Expand All @@ -150,6 +168,14 @@ void grout_link_change(struct gr_iface *gr_if, bool new, bool startup) {
vlan_info.vid = gr_vlan->vlan_id;
dplane_ctx_set_ifp_vlan_info(ctx, &vlan_info);
}
if (gr_vxlan) {
struct zebra_l2info_vxlan vi = {0};
vi.vni_info.iftype = ZEBRA_VXLAN_IF_VNI;
vi.vni_info.vni.vni = gr_vxlan->vni;
vi.ifindex_link = ifindex_grout_to_frr(gr_vxlan->encap_vrf_id);
vi.vtep_ip.s_addr = gr_vxlan->local;
dplane_ctx_set_ifp_vxlan_info(ctx, &vi);
}
Comment on lines +171 to +178
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find files related to VXLAN and VRF mappings
fd -t f "\.h$" -x grep -l "gr_iface_info_vxlan\|encap_vrf_id" {}

Repository: DPDK/grout

Length of output: 115


🏁 Script executed:

#!/bin/bash
# Search for encap_vrf_id definition and usage
rg -n "encap_vrf_id" --type=c -B2 -A2

Repository: DPDK/grout

Length of output: 6106


🏁 Script executed:

#!/bin/bash
# Find the ifindex_grout_to_frr function definition
rg -n "ifindex_grout_to_frr" --type=c -B2 -A5

Repository: DPDK/grout

Length of output: 4639


🏁 Script executed:

#!/bin/bash
# Search for VRF to FRR index mapping functions
rg -n "vrf_grout_to_frr|vrf.*to.*frr" --type=c

Repository: DPDK/grout

Length of output: 937


Line 175 incorrectly maps encap_vrf_id via ifindex_grout_to_frr().

encap_vrf_id is a VRF identifier (documented as "L3 domain for underlay routing"), not an interface ID. The code passes this VRF ID through an interface-index mapper, which returns IFINDEX_INTERNAL for unmapped or default VRFs, producing an invalid ifindex_link. The proper function for VRF conversion exists and is used elsewhere in the codebase (vrf_grout_to_frr()). This misconfiguration prevents VXLAN from correctly communicating its underlay VRF to FRR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frr/if_grout.c` around lines 171 - 178, The code incorrectly maps the VXLAN
underlay VRF by calling ifindex_grout_to_frr on gr_vxlan->encap_vrf_id (which is
a VRF id) resulting in IFINDEX_INTERNAL; replace the mapping with the proper VRF
converter vrf_grout_to_frr and assign its result to vi.ifindex_link so the VXLAN
underlay VRF (gr_vxlan->encap_vrf_id) is correctly conveyed to
dplane_ctx_set_ifp_vxlan_info via vi.ifindex_link.

} else {
dplane_ctx_set_op(ctx, DPLANE_OP_INTF_DELETE);
dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_QUEUED);
Expand Down
153 changes: 153 additions & 0 deletions frr/rt_grout.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
#include "log_grout.h"
#include "rt_grout.h"

#include <gr_l2.h>
#include <gr_srv6.h>

#include <lib/srv6.h>
#include <linux/neighbour.h>
#include <zebra/rib.h>
#include <zebra/table_manager.h>
#include <zebra_dplane_grout.h>
Expand Down Expand Up @@ -844,3 +846,154 @@ void grout_nexthop_change(bool new, struct gr_nexthop *gr_nh, bool startup) {
// nexthop_free() must *NOT* be used to preserve the nh_srv6 context.
free(nh);
}

void grout_macfdb_change(const struct gr_fdb_entry *fdb, bool new) {
struct zebra_dplane_ctx *ctx = dplane_ctx_alloc();
struct ethaddr mac;

gr_log_debug(
"%s bridge=%u iface=%u mac=%pEA vlan=%u vtep=%pI4",
new ? "add" : "del",
fdb->bridge_id,
fdb->iface_id,
&fdb->mac,
fdb->vlan_id,
&fdb->vtep
);

memcpy(&mac, &fdb->mac, sizeof(mac));

// Zebra's dplane API is asymmetric for FDB entries:
//
// - DPLANE_OP_MAC_INSTALL/DELETE is the downward path (zebra pushing
// MACs to dplane providers). The result handler is a no-op.
// - DPLANE_OP_NEIGH_INSTALL/DELETE is the upward path (dplane providers
// notifying zebra of learned MACs). This goes through
// zebra_neigh_macfdb_update() which triggers EVPN type-2 routes.
//
// It is NOT a bug to use dplane_ctx_mac_set_*() with DPLANE_OP_NEIGH_*
// ops. The macinfo and neigh fields are separate union members in the
// dplane context, and zebra's own netlink provider does the same thing
// (see rt_netlink.c netlink_macfdb_change()).
dplane_ctx_set_ns_id(ctx, GROUT_NS);
dplane_ctx_set_ifindex(ctx, ifindex_grout_to_frr(fdb->iface_id));
dplane_ctx_mac_set_addr(ctx, &mac);
dplane_ctx_mac_set_nhg_id(ctx, 0);
dplane_ctx_mac_set_ndm_state(ctx, NUD_REACHABLE);
dplane_ctx_mac_set_ndm_flags(ctx, NTF_MASTER);
dplane_ctx_mac_set_dst_present(ctx, fdb->vtep != 0);
dplane_ctx_mac_set_vtep_ip(ctx, &(struct in_addr) {fdb->vtep});
dplane_ctx_mac_set_vid(ctx, fdb->vlan_id);
dplane_ctx_mac_set_dp_static(ctx, fdb->flags & GR_FDB_F_STATIC);
dplane_ctx_mac_set_local_inactive(ctx, false);
dplane_ctx_mac_set_is_sticky(ctx, false);
dplane_ctx_set_op(ctx, new ? DPLANE_OP_NEIGH_INSTALL : DPLANE_OP_NEIGH_DELETE);

dplane_provider_enqueue_to_zebra(ctx);
}

enum zebra_dplane_result grout_macfdb_update_ctx(struct zebra_dplane_ctx *ctx) {
bool add = dplane_ctx_get_op(ctx) == DPLANE_OP_MAC_INSTALL;
uint32_t req_type;
size_t len;
void *req;
int ret;

gr_log_debug(
"%s bridge=%u iface=%u mac=%pEA vlan=%u vtep=%pI4",
add ? "add" : "del",
ifindex_frr_to_grout(dplane_ctx_get_ifindex(ctx)),
ifindex_frr_to_grout(dplane_ctx_get_ifindex(ctx)),
dplane_ctx_mac_get_addr(ctx),
dplane_ctx_mac_get_vlan(ctx),
dplane_ctx_mac_get_vtep_ip(ctx)
);
Comment on lines +902 to +910
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Debug log uses dplane_ctx_get_ifindex for both bridge= and iface=.

Lines 905–906 both call ifindex_frr_to_grout(dplane_ctx_get_ifindex(ctx)), so the log prints the same value for bridge and iface. The actual request (lines 922–923) correctly distinguishes them using dplane_ctx_mac_get_br_ifindex(ctx) for the bridge. The log should match.

Proposed fix
 	gr_log_debug(
 		"%s bridge=%u iface=%u mac=%pEA vlan=%u vtep=%pI4",
 		add ? "add" : "del",
-		ifindex_frr_to_grout(dplane_ctx_get_ifindex(ctx)),
+		ifindex_frr_to_grout(dplane_ctx_mac_get_br_ifindex(ctx)),
 		ifindex_frr_to_grout(dplane_ctx_get_ifindex(ctx)),
 		dplane_ctx_mac_get_addr(ctx),
 		dplane_ctx_mac_get_vlan(ctx),
 		dplane_ctx_mac_get_vtep_ip(ctx)
 	);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
gr_log_debug(
"%s bridge=%u iface=%u mac=%pEA vlan=%u vtep=%pI4",
add ? "add" : "del",
ifindex_frr_to_grout(dplane_ctx_get_ifindex(ctx)),
ifindex_frr_to_grout(dplane_ctx_get_ifindex(ctx)),
dplane_ctx_mac_get_addr(ctx),
dplane_ctx_mac_get_vlan(ctx),
dplane_ctx_mac_get_vtep_ip(ctx)
);
gr_log_debug(
"%s bridge=%u iface=%u mac=%pEA vlan=%u vtep=%pI4",
add ? "add" : "del",
ifindex_frr_to_grout(dplane_ctx_mac_get_br_ifindex(ctx)),
ifindex_frr_to_grout(dplane_ctx_get_ifindex(ctx)),
dplane_ctx_mac_get_addr(ctx),
dplane_ctx_mac_get_vlan(ctx),
dplane_ctx_mac_get_vtep_ip(ctx)
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frr/rt_grout.c` around lines 902 - 910, The debug log prints the same ifindex
for both bridge and iface; update the gr_log_debug call so bridge uses
dplane_ctx_mac_get_br_ifindex(ctx) and iface uses dplane_ctx_get_ifindex(ctx)
(i.e., replace the first/bridge argument to call dplane_ctx_mac_get_br_ifindex
instead of dplane_ctx_get_ifindex) so the log matches the request behavior; keep
the rest of the parameters (dplane_ctx_mac_get_addr, dplane_ctx_mac_get_vlan,
dplane_ctx_mac_get_vtep_ip) unchanged.


len = add ? sizeof(struct gr_fdb_add_req) : sizeof(struct gr_fdb_del_req);
req = calloc(1, len);
if (req == NULL) {
gr_log_err("failed to allocate memory");
return ZEBRA_DPLANE_REQUEST_FAILURE;
}

if (add) {
struct gr_fdb_add_req *add = req;
add->exist_ok = true;
add->fdb.iface_id = ifindex_frr_to_grout(dplane_ctx_get_ifindex(ctx));
add->fdb.bridge_id = ifindex_frr_to_grout(dplane_ctx_mac_get_br_ifindex(ctx));
add->fdb.vlan_id = dplane_ctx_mac_get_vlan(ctx);
add->fdb.flags = GR_FDB_F_EXTERN;
if (dplane_ctx_mac_get_dp_static(ctx))
add->fdb.flags |= GR_FDB_F_STATIC;
memcpy(&add->fdb.mac, dplane_ctx_mac_get_addr(ctx), sizeof(add->fdb.mac));
add->fdb.vtep = dplane_ctx_mac_get_vtep_ip(ctx)->s_addr;
req_type = GR_FDB_ADD;
} else {
struct gr_fdb_del_req *del = req;
del->missing_ok = true;
del->bridge_id = ifindex_frr_to_grout(dplane_ctx_mac_get_br_ifindex(ctx));
del->vlan_id = dplane_ctx_mac_get_vlan(ctx);
memcpy(&del->mac, dplane_ctx_mac_get_addr(ctx), sizeof(del->mac));
req_type = GR_FDB_DEL;
}

ret = grout_client_send_recv(req_type, len, req, NULL);

free(req);

return ret == 0 ? ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE;
}

enum zebra_dplane_result grout_vxlan_flood_update_ctx(struct zebra_dplane_ctx *ctx) {
const struct ipaddr *addr = dplane_ctx_neigh_get_ipaddr(ctx);
bool add = dplane_ctx_get_op(ctx) == DPLANE_OP_VTEP_ADD;
struct gr_flood_entry *entry;
uint32_t req_type;
size_t len;
void *req;
int ret;

gr_log_debug(
"%s %pIA vni=%u vrf=%u",
add ? "add" : "del",
addr,
dplane_ctx_neigh_get_vni(ctx),
vrf_frr_to_grout(dplane_ctx_get_vrf(ctx))
);

if (addr->ipa_type != IPADDR_V4) {
gr_log_err("IPv6 flood list entries are not supported");
return ZEBRA_DPLANE_REQUEST_FAILURE;
}

len = add ? sizeof(struct gr_flood_add_req) : sizeof(struct gr_flood_del_req);

req = calloc(1, len);
if (req == NULL) {
gr_log_err("failed to allocate memory");
return ZEBRA_DPLANE_REQUEST_FAILURE;
}

if (add) {
struct gr_flood_add_req *a = req;
entry = &a->entry;
a->exist_ok = true;
req_type = GR_FLOOD_ADD;
} else {
struct gr_flood_del_req *d = req;
entry = &d->entry;
d->missing_ok = true;
req_type = GR_FLOOD_DEL;
}

entry->type = GR_FLOOD_T_VTEP;
entry->vrf_id = vrf_frr_to_grout(dplane_ctx_get_vrf(ctx));
entry->vtep.vni = dplane_ctx_neigh_get_vni(ctx);
entry->vtep.addr = addr->ipaddr_v4.s_addr;

ret = grout_client_send_recv(req_type, len, req, NULL);

free(req);

return ret == 0 ? ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE;
}
5 changes: 5 additions & 0 deletions frr/rt_grout.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <gr_ip4.h>
#include <gr_ip6.h>
#include <gr_l2.h>

#include <zebra/zebra_dplane.h>

Expand All @@ -13,3 +14,7 @@ void grout_route6_change(bool new, struct gr_ip6_route *gr_r6);
enum zebra_dplane_result grout_add_del_route(struct zebra_dplane_ctx *ctx);
enum zebra_dplane_result grout_add_del_nexthop(struct zebra_dplane_ctx *ctx);
void grout_nexthop_change(bool new, struct gr_nexthop *gr_nh, bool startup);

void grout_macfdb_change(const struct gr_fdb_entry *fdb, bool new);
enum zebra_dplane_result grout_macfdb_update_ctx(struct zebra_dplane_ctx *ctx);
enum zebra_dplane_result grout_vxlan_flood_update_ctx(struct zebra_dplane_ctx *ctx);
Loading