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
2 changes: 1 addition & 1 deletion api/gr_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include <stdlib.h>

// Must be bumped when making non-backward compatible changes in API headers
#define GR_API_VERSION 2
#define GR_API_VERSION 4

// API request header.
struct gr_api_request {
Expand Down
18 changes: 17 additions & 1 deletion modules/infra/api/gr_infra.h
Original file line number Diff line number Diff line change
Expand Up @@ -443,14 +443,30 @@ struct gr_graph_dump_resp {

GR_REQ(GR_GRAPH_DUMP, struct gr_graph_dump_req, struct gr_graph_dump_resp);

typedef enum : uint16_t {
GR_GRAPH_SET_RX_BURST = GR_BIT16(0),
GR_GRAPH_SET_VECTOR = GR_BIT16(1),
GR_GRAPH_SET_ICMP_ERROR = GR_BIT16(2),
GR_GRAPH_SET_ARP = GR_BIT16(3),
GR_GRAPH_SET_ICMP = GR_BIT16(4),
} gr_graph_conf_set_attr_t;

struct gr_graph_conf {
uint16_t rx_burst_max; // default 64, max 256
uint16_t vector_max; // default 64, max 256
uint16_t icmp_error_rate; // ICMP errors/sec per node per worker, 0 = no limit, default 1000
uint16_t arp_rate; // ARP packets/sec per worker, 0 = no limit, default 1000
uint16_t icmp_rate; // ICMP/ICMPv6 input packets/sec per worker, 0 = no limit, default 1000
};

GR_REQ(GR_GRAPH_CONF_GET, struct gr_empty, struct gr_graph_conf);

GR_REQ(GR_GRAPH_CONF_SET, struct gr_graph_conf, struct gr_empty);
struct gr_graph_conf_set_req {
struct gr_graph_conf;
gr_graph_conf_set_attr_t set_attrs;
};

GR_REQ(GR_GRAPH_CONF_SET, struct gr_graph_conf_set_req, struct gr_empty);

// packet tracing //////////////////////////////////////////////////////////////

Expand Down
46 changes: 41 additions & 5 deletions modules/infra/cli/graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,31 @@
#include <stdio.h>

static cmd_status_t graph_conf_set(struct gr_api_client *c, const struct ec_pnode *p) {
struct gr_graph_conf req = {0};
struct gr_graph_conf_set_req req = {.set_attrs = 0};

if (arg_u16(p, "VECTOR", &req.vector_max) < 0 && errno != ENOENT)
if (arg_u16(p, "VECTOR", &req.vector_max) == 0)
req.set_attrs |= GR_GRAPH_SET_VECTOR;
else if (errno != ENOENT)
return CMD_ERROR;
if (arg_u16(p, "BURST", &req.rx_burst_max) < 0 && errno != ENOENT)

if (arg_u16(p, "BURST", &req.rx_burst_max) == 0)
req.set_attrs |= GR_GRAPH_SET_RX_BURST;
else if (errno != ENOENT)
return CMD_ERROR;

if (arg_u16(p, "ICMP_ERROR_RATE", &req.icmp_error_rate) == 0)
req.set_attrs |= GR_GRAPH_SET_ICMP_ERROR;
else if (errno != ENOENT)
return CMD_ERROR;

if (arg_u16(p, "ARP_RATE", &req.arp_rate) == 0)
req.set_attrs |= GR_GRAPH_SET_ARP;
else if (errno != ENOENT)
return CMD_ERROR;

if (arg_u16(p, "ICMP_RATE", &req.icmp_rate) == 0)
req.set_attrs |= GR_GRAPH_SET_ICMP;
else if (errno != ENOENT)
return CMD_ERROR;

if (gr_api_client_send_recv(c, GR_GRAPH_CONF_SET, sizeof(req), &req, NULL) < 0)
Expand All @@ -38,6 +58,9 @@ static cmd_status_t graph_conf_show(struct gr_api_client *c, const struct ec_pno
struct gr_object *o = gr_object_new(NULL);
gr_object_field(o, "vector_max", GR_DISP_INT, "%u", sizes->vector_max);
gr_object_field(o, "rx_burst_max", GR_DISP_INT, "%u", sizes->rx_burst_max);
gr_object_field(o, "icmp_error_rate", GR_DISP_INT, "%u", sizes->icmp_error_rate);
gr_object_field(o, "arp_rate", GR_DISP_INT, "%u", sizes->arp_rate);
gr_object_field(o, "icmp_rate", GR_DISP_INT, "%u", sizes->icmp_rate);
gr_object_free(o);

free(resp_ptr);
Expand Down Expand Up @@ -76,14 +99,27 @@ static int ctx_init(struct ec_node *root) {

ret = CLI_COMMAND(
CONF_CTX(root),
"set (vector-max VECTOR),(rx-burst-max BURST)",
"set (vector-max VECTOR),(rx-burst-max BURST),(icmp-error-rate ICMP_ERROR_RATE),"
"(arp-rate ARP_RATE),(icmp-rate ICMP_RATE)",
graph_conf_set,
"Configure maximum burst sizes of the packet processing graph.",
"Configure packet processing graph parameters.",
with_help(
"Maximum size of graph vectors.", ec_node_uint("VECTOR", 1, UINT16_MAX, 10)
),
with_help(
"Maximum size of RX queue burst.", ec_node_uint("BURST", 1, UINT16_MAX, 10)
),
with_help(
"ICMP errors/sec per node per worker (0 = no limit).",
ec_node_uint("ICMP_ERROR_RATE", 0, UINT16_MAX, 10)
),
with_help(
"ARP packets/sec per worker (0 = no limit).",
ec_node_uint("ARP_RATE", 0, UINT16_MAX, 10)
),
with_help(
"ICMP/ICMPv6 input packets/sec per worker (0 = no limit).",
ec_node_uint("ICMP_RATE", 0, UINT16_MAX, 10)
Comment on lines +112 to +122

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What about setting the lower bound to RTE_GRAPH_BURST_SIZE, to have an explicit value for the user ?

With the current code: ctx->tokens -= RTE_MIN(ctx->tokens, nb_pkts);, if the user sets the number of tokens to 1, we may still send up to RTE_GRAPH_BURST_SIZE errors, or ICMP packets, etc.

@rjarry rjarry Apr 23, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

But then how would you disable it?

Indeed, the limit is soft and we allow the full burst to pass even if there is less than nb_pkts left in the bucket. But since it is transient, I thought it was fine.

)
);
if (ret < 0)
Expand Down
32 changes: 24 additions & 8 deletions modules/infra/control/graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,12 @@ static struct iface_info_port *find_port(vec struct iface_info_port **ports, uin
return port;
}

static struct gr_graph_conf graph_conf = {
struct gr_graph_conf graph_conf = {
.rx_burst_max = 64,
.vector_max = 64,
.icmp_error_rate = 1000,
.arp_rate = 1000,
.icmp_rate = 1000,
};

static int
Expand Down Expand Up @@ -610,19 +613,32 @@ static struct api_out graph_conf_get(const void * /*request*/, struct api_ctx *)
}

static struct api_out graph_conf_set(const void *request, struct api_ctx *) {
const struct gr_graph_conf *req = request;
struct gr_graph_conf prev = graph_conf;
const struct gr_graph_conf_set_req *req = request;
vec struct iface_info_port **ports = NULL;
struct gr_graph_conf prev = graph_conf;
struct iface *iface = NULL;
int ret;

if (req->rx_burst_max > RTE_GRAPH_BURST_SIZE || req->vector_max > RTE_GRAPH_BURST_SIZE)
return api_out(EOVERFLOW, 0, NULL);

if (req->rx_burst_max > 0)
if (req->set_attrs & GR_GRAPH_SET_RX_BURST) {
if (req->rx_burst_max == 0)
return api_out(EDOM, 0, NULL);
if (req->rx_burst_max > RTE_GRAPH_BURST_SIZE)
return api_out(EOVERFLOW, 0, NULL);
graph_conf.rx_burst_max = req->rx_burst_max;
if (req->vector_max > 0)
}
if (req->set_attrs & GR_GRAPH_SET_VECTOR) {
if (req->vector_max == 0)
return api_out(EDOM, 0, NULL);
if (req->vector_max > RTE_GRAPH_BURST_SIZE)
return api_out(EOVERFLOW, 0, NULL);
graph_conf.vector_max = req->vector_max;
}
if (req->set_attrs & GR_GRAPH_SET_ICMP_ERROR)
graph_conf.icmp_error_rate = req->icmp_error_rate;
if (req->set_attrs & GR_GRAPH_SET_ARP)
graph_conf.arp_rate = req->arp_rate;
if (req->set_attrs & GR_GRAPH_SET_ICMP)
graph_conf.icmp_rate = req->icmp_rate;

if (graph_conf.rx_burst_max == prev.rx_burst_max
&& graph_conf.vector_max == prev.vector_max)
Expand Down
2 changes: 2 additions & 0 deletions modules/infra/control/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ rte_node_enqueue_x1(struct rte_graph *, struct rte_node *, rte_edge_t next, void

rte_edge_t gr_node_attach_parent(const char *parent, const char *node);

extern struct gr_graph_conf graph_conf;

uint16_t drop_packets(struct rte_graph *, struct rte_node *, void **, uint16_t);
int drop_format(char *buf, size_t buf_len, const void *data, size_t data_len);

Expand Down
33 changes: 33 additions & 0 deletions modules/infra/datapath/datapath.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,37 @@

#pragma once

#include <rte_cycles.h>

#include <stdint.h>

void *gr_datapath_loop(void *priv);

struct rate_limit_ctx {
uint16_t tokens;
uint64_t last_refill;
} __attribute__((packed));

static inline bool
rate_limited(struct rate_limit_ctx *ctx, const uint16_t max_rate, const uint16_t nb_pkts) {
uint64_t now = rte_rdtsc();
uint16_t add;

assert(ctx != NULL);

if (max_rate == 0)
return false;

add = (now - ctx->last_refill) * max_rate / rte_get_tsc_hz();
if (add > 0) {
ctx->tokens = RTE_MIN((uint32_t)ctx->tokens + add, (uint32_t)max_rate);
ctx->last_refill = now;
}

if (ctx->tokens == 0)
return true;

ctx->tokens -= RTE_MIN(ctx->tokens, nb_pkts);

return false;
}
19 changes: 19 additions & 0 deletions modules/ip/datapath/arp_input.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2024 Robin Jarry

#include "datapath.h"
#include "eth.h"
#include "graph.h"
#include "mbuf.h"
Expand All @@ -9,20 +10,29 @@
#include <rte_arp.h>
#include <rte_ether.h>

GR_NODE_CTX_TYPE(arp_input_ctx, { struct rate_limit_ctx limit; });

enum {
OP_REQUEST = 0,
OP_REPLY,
OP_UNSUPP,
PROTO_UNSUPP,
RATE_LIMITED,
EDGE_COUNT,
};

static uint16_t
arp_input_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) {
struct arp_input_ctx *ctx = arp_input_ctx(node);
struct rte_arp_hdr *arp, *t;
struct rte_mbuf *mbuf;
rte_edge_t edge;

if (rate_limited(&ctx->limit, graph_conf.arp_rate, nb_objs)) {
rte_node_next_stream_move(graph, node, RATE_LIMITED);
return nb_objs;
}

for (uint16_t i = 0; i < nb_objs; i++) {
mbuf = objs[i];

Expand Down Expand Up @@ -58,6 +68,13 @@ arp_input_process(struct rte_graph *graph, struct rte_node *node, void **objs, u
return nb_objs;
}

static int arp_input_init(const struct rte_graph *, struct rte_node *node) {
struct arp_input_ctx *ctx = arp_input_ctx(node);
ctx->limit.tokens = graph_conf.arp_rate;
ctx->limit.last_refill = rte_rdtsc();
return 0;
}

static void arp_input_register(void) {
gr_eth_input_add_type(RTE_BE16(RTE_ETHER_TYPE_ARP), "arp_input");
}
Expand All @@ -66,13 +83,15 @@ static struct rte_node_register node = {
.name = "arp_input",

.process = arp_input_process,
.init = arp_input_init,

.nb_edges = EDGE_COUNT,
.next_nodes = {
[OP_REQUEST] = "arp_input_request",
[OP_REPLY] = "arp_input_reply",
[OP_UNSUPP] = "arp_input_op_unsupp",
[PROTO_UNSUPP] = "arp_input_proto_unsupp",
[RATE_LIMITED] = "error_rate_limited",
},
};

Expand Down
19 changes: 19 additions & 0 deletions modules/ip/datapath/icmp_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright (c) 2024 Robin Jarry

#include "control_output.h"
#include "datapath.h"
#include "graph.h"
#include "ip4_datapath.h"
#include "log.h"
Expand All @@ -12,11 +13,14 @@

#include <rte_icmp.h>

GR_NODE_CTX_TYPE(icmp_input_ctx, { struct rate_limit_ctx limit; });

enum {
OUTPUT = 0,
CONTROL,
INVALID,
UNSUPPORTED,
RATE_LIMITED,
EDGE_COUNT,
};

Expand All @@ -26,13 +30,19 @@ static control_queue_cb_t icmp_cb[UINT8_MAX];

static uint16_t
icmp_input_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) {
struct icmp_input_ctx *ctx = icmp_input_ctx(node);
struct ip_local_mbuf_data *ip_data;
struct rte_icmp_hdr *icmp;
struct rte_mbuf *mbuf;
rte_edge_t edge;
uint16_t cksum;
ip4_addr_t ip;

if (rate_limited(&ctx->limit, graph_conf.icmp_rate, nb_objs)) {
rte_node_next_stream_move(graph, node, RATE_LIMITED);
return nb_objs;
}

for (uint16_t i = 0; i < nb_objs; i++) {
mbuf = objs[i];
icmp = rte_pktmbuf_mtod(mbuf, struct rte_icmp_hdr *);
Expand Down Expand Up @@ -80,6 +90,13 @@ void icmp_input_register_callback(uint8_t icmp_type, control_queue_cb_t cb) {
icmp_cb[icmp_type] = cb;
}

static int icmp_input_init(const struct rte_graph *, struct rte_node *node) {
struct icmp_input_ctx *ctx = icmp_input_ctx(node);
ctx->limit.tokens = graph_conf.icmp_rate;
ctx->limit.last_refill = rte_rdtsc();
return 0;
}

static void icmp_input_register(void) {
ip_input_local_add_proto(IPPROTO_ICMP, "icmp_input");
}
Expand All @@ -88,13 +105,15 @@ static struct rte_node_register icmp_input_node = {
.name = "icmp_input",

.process = icmp_input_process,
.init = icmp_input_init,

.nb_edges = EDGE_COUNT,
.next_nodes = {
[OUTPUT] = "icmp_output",
[CONTROL] = "control_output",
[INVALID] = "icmp_input_invalid",
[UNSUPPORTED] = "icmp_input_unsupported",
[RATE_LIMITED] = "error_rate_limited",
},
};

Expand Down
Loading
Loading