Skip to content
Merged
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
3 changes: 3 additions & 0 deletions api/gr_net_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@
#endif

typedef enum : uint8_t {
GR_AF_UNSPEC = AF_UNSPEC,
GR_AF_IP4 = AF_INET,
GR_AF_IP6 = AF_INET6,
} addr_family_t;

static inline const char *gr_af_name(addr_family_t af) {
switch (af) {
case GR_AF_UNSPEC:
return "Unspec";
case GR_AF_IP4:
return "IPv4";
case GR_AF_IP6:
Expand Down
4 changes: 4 additions & 0 deletions frr/if_grout.c
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ void grout_interface_addr_dplane(struct gr_nexthop *gr_nh, bool new) {
p.family = AF_INET6;
memcpy(&p.u.prefix6, &gr_nh->ipv6, sizeof(p.u.prefix6));
break;
case GR_AF_UNSPEC:
gr_log_err("interface with addr family UNSPEC are not supported");
dplane_ctx_fini(&ctx);
return;
}
dplane_ctx_set_intf_addr(ctx, &p);
dplane_ctx_set_intf_metric(ctx, METRIC_MAX);
Expand Down
17 changes: 14 additions & 3 deletions frr/rt_grout.c
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ static int grout_gr_nexthop_to_frr_nexthop(
*nh_family = AF_INET6;
memcpy(&nh->gate.ipv6, &gr_nh->ipv6, sz);
break;
case GR_AF_UNSPEC:
nh->type = NEXTHOP_TYPE_IFINDEX;
*nh_family = AF_UNSPEC;
break;
default:
gr_log_debug("inval nexthop family %u, nexthop not sync", gr_nh->af);
return -1;
Expand Down Expand Up @@ -294,7 +298,7 @@ static void grout_route_change(
return;
}

if (nh_family != family) {
if (nh_family != AF_UNSPEC && nh_family != family) {
Comment on lines 298 to +301
Copy link

Choose a reason for hiding this comment

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

Logic bug: comparing the pointer nh_family to AF_UNSPEC/family rather than its value. You need to dereference (*nh_family) in these comparisons.

gr_log_debug(
"nexthop family %u different that route family %u nexthop, "
"ignoring",
Expand Down Expand Up @@ -850,8 +854,11 @@ enum zebra_dplane_result grout_add_del_nexthop(struct zebra_dplane_ctx *ctx) {
gr_log_debug("add nexthop id %u gw %pI6", nh_id, &gr_nh->ipv6);
break;
case NEXTHOP_TYPE_IFINDEX:
gr_log_debug("add nexthop id %u ifindex %u", nh_id, gr_nh->iface_id);
return ZEBRA_DPLANE_REQUEST_FAILURE;
// dplane_ctx_get_nhe_afi(ctx) returns AFI_IP for a nexthop with no gateway
// force to UNSPEC for grout
gr_nh->af = GR_AF_UNSPEC;
gr_log_debug("add nexthop id %u with ifindex %u", nh_id, gr_nh->iface_id);
break;
default:
gr_log_err("impossible to add nexthop %u (type %u not supported)", nh_id, nh->type);
return ZEBRA_DPLANE_REQUEST_FAILURE;
Expand Down Expand Up @@ -890,6 +897,10 @@ void grout_nexthop_change(bool new, struct gr_nexthop *gr_nh) {
return;
}

// kernel set INET4 when no gateway, let's do the same
if (family == AF_UNSPEC)
family = AF_INET;

afi = family2afi(family);
type = origin2zebra(gr_nh->origin, family, false);
SET_FLAG(nh.flags, NEXTHOP_FLAG_ACTIVE);
Expand Down
2 changes: 2 additions & 0 deletions frr/zebra_dplane_grout.c
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ static void dplane_read_notifications(struct event *event) {
gr_evt_to_str(gr_e->ev_type)
);
break;
case GR_AF_UNSPEC:
break;
}

grout_interface_addr_dplane(gr_nh, new);
Expand Down
102 changes: 102 additions & 0 deletions main/gr_id_pool.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2025 Maxime Leroy, Free Mobile
// Simple ID allocator built on top of DPDK’s rte_bitmap
// bit 1 ⇒ ID free
// bit 0 ⇒ ID reserved
// single writer, any number of readers

#pragma once

#include <gr_errno.h>

#include <rte_bitmap.h>
#include <rte_malloc.h>

#include <stdint.h>

struct gr_id_pool {
uint32_t max_ids;
uint32_t used;
Comment on lines +16 to +19
Copy link

Choose a reason for hiding this comment

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

Member used is non-atomic and is read concurrently by multiple threads (single writer, multiple readers). This is a data-race/UB; consider making it an atomic or protecting access with a lock.

// struct rte_bitmap must be aligned on a cache line
// it's required by rte_bitmap library.
struct rte_bitmap bmp __rte_cache_aligned;
};

static inline struct gr_id_pool *gr_id_pool_create(const char *id_pool_name, uint32_t max_ids) {
size_t p_size, b_size;
struct gr_id_pool *p;

b_size = rte_bitmap_get_memory_footprint(max_ids);
p_size = sizeof(struct gr_id_pool) - sizeof(struct rte_bitmap) + b_size;
Comment on lines +27 to +30
Copy link

Choose a reason for hiding this comment

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

Potential integer overflow computing p_size = sizeof(...) - sizeof(...) + b_size. Validate that b_size isn’t large enough to wrap p_size.

p = rte_zmalloc(id_pool_name, p_size, RTE_CACHE_LINE_SIZE);
if (!p)
return NULL;

if (!rte_bitmap_init_with_all_set(max_ids, (uint8_t *)&p->bmp, b_size)) {
rte_free(p);
return NULL;
}

p->max_ids = max_ids;
p->used = 0;
return p;
}

static inline void gr_id_pool_destroy(struct gr_id_pool *p) {
if (!p)
return;

rte_free(p);
}

static inline uint32_t gr_id_pool_used(struct gr_id_pool *p) {
return p->used;
}

static inline uint32_t gr_id_pool_avail(struct gr_id_pool *p) {
return p->max_ids - p->used;
}

// Get the lowest‑numbered free ID; 0 if none
static inline uint32_t gr_id_pool_get(struct gr_id_pool *p) {
uint64_t slab = 0;
uint32_t pos = 0;
uint32_t bit;

__rte_bitmap_scan_init(&p->bmp);
if (!rte_bitmap_scan(&p->bmp, &pos, &slab))
return 0; // pool is full

bit = pos + rte_ctz64(slab);
rte_bitmap_clear(&p->bmp, bit); // mark used

p->used++;
return bit + 1;
}

// Reserve a user‑chosen ID. Returns 0 on success, <0 on error
static inline int gr_id_pool_book(struct gr_id_pool *p, uint32_t id) {
if (id == 0 || id > p->max_ids)
return errno_set(EINVAL);

// already used
if (!rte_bitmap_get(&p->bmp, id - 1))
return errno_set(EEXIST);

rte_bitmap_clear(&p->bmp, id - 1);
p->used++;
return 0;
}

// Put an ID back to the pool
static inline int gr_id_pool_put(struct gr_id_pool *p, uint32_t id) {
if (id == 0 || id > p->max_ids)
return errno_set(EINVAL);

if (rte_bitmap_get(&p->bmp, id - 1))
return errno_set(EALREADY);

rte_bitmap_set(&p->bmp, id - 1);
p->used--;
return 0;
}
51 changes: 20 additions & 31 deletions modules/infra/api/nexthop.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,25 @@ static struct api_out nh_add(const void *request, void ** /*response*/) {

int ret;

base.flags = 0;
switch (base.af) {
case GR_AF_IP4:
if (base.ipv4 == 0)
return api_out(EDESTADDRREQ, 0);

base.flags |= GR_NH_F_GATEWAY;
break;
case GR_AF_IP6:
if (rte_ipv6_addr_is_unspec(&base.ipv6))
return api_out(EDESTADDRREQ, 0);

base.flags |= GR_NH_F_GATEWAY;
break;
case GR_AF_UNSPEC:
if (base.ipv4 || !rte_ipv6_addr_is_unspec(&base.ipv6))
return api_out(EINVAL, 0);

base.flags |= GR_NH_F_LINK | GR_NH_F_STATIC;
break;
default:
return api_out(ENOPROTOOPT, 0);
Expand All @@ -67,10 +78,12 @@ static struct api_out nh_add(const void *request, void ** /*response*/) {

base.type = GR_NH_T_L3;
base.state = GR_NH_S_NEW;
base.flags = 0;
if (!rte_is_zero_ether_addr(&base.mac)) {
if (base.af == GR_AF_UNSPEC)
return api_out(EINVAL, 0);

base.state = GR_NH_S_REACHABLE;
base.flags = GR_NH_F_STATIC;
base.flags |= GR_NH_F_STATIC;
}

if (base.nh_id != GR_NH_ID_UNSET)
Expand Down Expand Up @@ -132,35 +145,11 @@ static struct api_out nh_del(const void *request, void ** /*response*/) {
const struct nexthop_af_ops *ops;
struct nexthop *nh;

if (req->nh.nh_id != GR_NH_ID_UNSET) {
nh = nexthop_lookup_by_id(req->nh.nh_id);
if (nh == NULL) {
if (req->missing_ok)
return api_out(0, 0);
return api_out(ENOENT, 0);
}
} else {
switch (req->nh.af) {
case GR_AF_IP4:
if (req->nh.ipv4 == 0)
return api_out(EDESTADDRREQ, 0);
break;
case GR_AF_IP6:
if (rte_ipv6_addr_is_unspec(&req->nh.ipv6))
return api_out(EDESTADDRREQ, 0);
break;
default:
return api_out(ENOPROTOOPT, 0);
}
if (req->nh.vrf_id >= MAX_VRFS)
return api_out(EOVERFLOW, 0);

nh = nexthop_lookup(req->nh.af, req->nh.vrf_id, req->nh.iface_id, &req->nh.addr);
if (nh == NULL) {
if (errno == ENOENT && req->missing_ok)
return api_out(0, 0);
return api_out(errno, 0);
}
nh = nexthop_lookup_by_id(req->nh.nh_id);
if (nh == NULL) {
if (req->missing_ok)
return api_out(0, 0);
return api_out(ENOENT, 0);
}

if ((nh->flags & (GR_NH_F_LOCAL | GR_NH_F_LINK | GR_NH_F_GATEWAY)) || nh->ref_count > 1)
Expand Down
38 changes: 16 additions & 22 deletions modules/infra/cli/nexthop.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ static cmd_status_t nh_add(const struct gr_api_client *c, const struct ec_pnode
struct gr_nh_add_req req = {.exist_ok = true, .nh.origin = GR_NH_ORIGIN_USER};
struct gr_iface iface;

if (arg_u32(p, "ID", &req.nh.nh_id) < 0 && errno != ENOENT)
return CMD_ERROR;

switch (arg_ip4(p, "IP", &req.nh.ipv4)) {
case 0:
req.nh.af = GR_AF_IP4;
Expand All @@ -72,16 +75,15 @@ static cmd_status_t nh_add(const struct gr_api_client *c, const struct ec_pnode
req.nh.af = GR_AF_IP6;
break;
default:
return CMD_ERROR;
req.nh.af = GR_AF_UNSPEC;
break;
}

if (iface_from_name(c, arg_str(p, "IFACE"), &iface) < 0)
return CMD_ERROR;
req.nh.iface_id = iface.id;
req.nh.vrf_id = iface.vrf_id;

if (arg_u32(p, "ID", &req.nh.nh_id) < 0 && errno != ENOENT)
return CMD_ERROR;

if (arg_eth_addr(p, "MAC", &req.nh.mac) < 0 && errno != ENOENT)
return CMD_ERROR;

Expand All @@ -94,19 +96,9 @@ static cmd_status_t nh_add(const struct gr_api_client *c, const struct ec_pnode
static cmd_status_t nh_del(const struct gr_api_client *c, const struct ec_pnode *p) {
struct gr_nh_del_req req = {.missing_ok = true};

switch (arg_ip4(p, "IP", &req.nh.ipv4)) {
case 0:
req.nh.af = GR_AF_IP4;
break;
case -EINVAL:
if (arg_ip6(p, "IP", &req.nh.ipv6) < 0)
return CMD_ERROR;
req.nh.af = GR_AF_IP6;
break;
default:
if (arg_u32(p, "ID", &req.nh.nh_id) < 0 && errno != ENOENT)
return CMD_ERROR;
}
if (arg_u32(p, "ID", &req.nh.nh_id) < 0)
return CMD_ERROR;

if (arg_u16(p, "VRF", &req.nh.vrf_id) < 0 && errno != ENOENT)
return CMD_ERROR;

Expand Down Expand Up @@ -164,7 +156,10 @@ static cmd_status_t nh_list(const struct gr_api_client *c, const struct ec_pnode
scols_line_set_data(line, 1, "");
scols_line_sprintf(line, 2, "%s", gr_nh_type_name(nh));
scols_line_sprintf(line, 3, "%s", gr_af_name(nh->af));
scols_line_sprintf(line, 4, ADDR_F, ADDR_W(nh->af), &nh->addr);
if (nh->af == GR_AF_UNSPEC)
scols_line_set_data(line, 4, "");
else
scols_line_sprintf(line, 4, ADDR_F, ADDR_W(nh->af), &nh->addr);

if (nh->state == GR_NH_S_REACHABLE)
scols_line_sprintf(line, 5, ETH_F, &nh->mac);
Expand Down Expand Up @@ -251,10 +246,10 @@ static int ctx_init(struct ec_node *root) {

ret = CLI_COMMAND(
CLI_CONTEXT(root, CTX_ADD),
"nexthop IP iface IFACE [id ID] [mac MAC]",
"nexthop [id ID] [address IP] iface IFACE [mac MAC]",
nh_add,
"Add a new next hop.",
with_help("IPv4 address.", ec_node_re("IP", IP_ANY_RE)),
with_help("IPv4/6 address.", ec_node_re("IP", IP_ANY_RE)),
with_help("Ethernet address.", ec_node_re("MAC", ETH_ADDR_RE)),
with_help("Nexthop ID.", ec_node_uint("ID", 1, UINT32_MAX - 1, 10)),
with_help("Output interface.", ec_node_dyn("IFACE", complete_iface_names, NULL))
Expand All @@ -263,10 +258,9 @@ static int ctx_init(struct ec_node *root) {
return ret;
ret = CLI_COMMAND(
CLI_CONTEXT(root, CTX_DEL),
"nexthop (id ID)|IP [vrf VRF]",
"nexthop ID [vrf VRF]",
nh_del,
"Delete a next hop.",
with_help("IPv4 address.", ec_node_re("IP", IP_ANY_RE)),
with_help("Nexthop ID.", ec_node_uint("ID", 1, UINT32_MAX - 1, 10)),
with_help("L3 routing domain ID.", ec_node_uint("VRF", 0, UINT16_MAX - 1, 10))
);
Expand Down
Loading