Skip to content

Commit c8cc65e

Browse files
committed
ip,ip6: add rate limiting for ICMP errors, ARP and ICMP input
Under a flood of bad packets (TTL=1, no-route, etc.), every packet triggers expensive ICMP error construction: address lookups, header prepend with potential memmove, and checksum computation. Similarly, ARP and ICMP/ICMPv6 input processing can saturate the control plane thread when flooded. None of this is rate limited today. Add a per-node token bucket to all ICMP error nodes, ARP input, and ICMP/ICMPv6 input nodes. The bucket refills once per second and the check runs before any per-packet work. When the bucket is empty, the whole vector is moved to a dedicated drop node via rte_node_next_stream_move(), avoiding any unnecessary processing. Three separate rates are configurable via the graph config API: icmp-error-rate, arp-rate and icmp-rate. All default to 1000 packets/sec per node per worker. Setting a rate to 0 disables the limit. Rate changes take effect immediately without graph reload. Signed-off-by: Robin Jarry <rjarry@redhat.com>
1 parent 84e372a commit c8cc65e

11 files changed

Lines changed: 175 additions & 7 deletions

File tree

api/gr_api.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#include <stdlib.h>
1212

1313
// Must be bumped when making non-backward compatible changes in API headers
14-
#define GR_API_VERSION 2
14+
#define GR_API_VERSION 3
1515

1616
// API request header.
1717
struct gr_api_request {

modules/infra/api/gr_infra.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,11 +404,17 @@ GR_REQ(GR_GRAPH_DUMP, struct gr_graph_dump_req, struct gr_graph_dump_resp);
404404
typedef enum : uint16_t {
405405
GR_GRAPH_SET_RX_BURST = GR_BIT16(0),
406406
GR_GRAPH_SET_VECTOR = GR_BIT16(1),
407+
GR_GRAPH_SET_ICMP_ERROR = GR_BIT16(2),
408+
GR_GRAPH_SET_ARP = GR_BIT16(3),
409+
GR_GRAPH_SET_ICMP = GR_BIT16(4),
407410
} gr_graph_conf_set_attr_t;
408411

409412
struct gr_graph_conf {
410413
uint16_t rx_burst_max; // default 64, max 256
411414
uint16_t vector_max; // default 64, max 256
415+
uint16_t icmp_error_rate; // ICMP errors/sec per node per worker, 0 = no limit, default 1000
416+
uint16_t arp_rate; // ARP packets/sec per worker, 0 = no limit, default 1000
417+
uint16_t icmp_rate; // ICMP/ICMPv6 input packets/sec per worker, 0 = no limit, default 1000
412418
};
413419

414420
GR_REQ(GR_GRAPH_CONF_GET, struct gr_empty, struct gr_graph_conf);

modules/infra/cli/graph.c

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@ static cmd_status_t graph_conf_set(struct gr_api_client *c, const struct ec_pnod
2525
else if (errno != ENOENT)
2626
return CMD_ERROR;
2727

28+
if (arg_u16(p, "ICMP_ERROR_RATE", &req.icmp_error_rate) == 0)
29+
req.set_attrs |= GR_GRAPH_SET_ICMP_ERROR;
30+
else if (errno != ENOENT)
31+
return CMD_ERROR;
32+
33+
if (arg_u16(p, "ARP_RATE", &req.arp_rate) == 0)
34+
req.set_attrs |= GR_GRAPH_SET_ARP;
35+
else if (errno != ENOENT)
36+
return CMD_ERROR;
37+
38+
if (arg_u16(p, "ICMP_RATE", &req.icmp_rate) == 0)
39+
req.set_attrs |= GR_GRAPH_SET_ICMP;
40+
else if (errno != ENOENT)
41+
return CMD_ERROR;
42+
2843
if (gr_api_client_send_recv(c, GR_GRAPH_CONF_SET, sizeof(req), &req, NULL) < 0)
2944
return CMD_ERROR;
3045

@@ -43,6 +58,9 @@ static cmd_status_t graph_conf_show(struct gr_api_client *c, const struct ec_pno
4358
struct gr_object *o = gr_object_new(NULL);
4459
gr_object_field(o, "vector_max", GR_DISP_INT, "%u", sizes->vector_max);
4560
gr_object_field(o, "rx_burst_max", GR_DISP_INT, "%u", sizes->rx_burst_max);
61+
gr_object_field(o, "icmp_error_rate", GR_DISP_INT, "%u", sizes->icmp_error_rate);
62+
gr_object_field(o, "arp_rate", GR_DISP_INT, "%u", sizes->arp_rate);
63+
gr_object_field(o, "icmp_rate", GR_DISP_INT, "%u", sizes->icmp_rate);
4664
gr_object_free(o);
4765

4866
free(resp_ptr);
@@ -81,14 +99,27 @@ static int ctx_init(struct ec_node *root) {
8199

82100
ret = CLI_COMMAND(
83101
CONF_CTX(root),
84-
"set (vector-max VECTOR),(rx-burst-max BURST)",
102+
"set (vector-max VECTOR),(rx-burst-max BURST),(icmp-error-rate ICMP_ERROR_RATE),"
103+
"(arp-rate ARP_RATE),(icmp-rate ICMP_RATE)",
85104
graph_conf_set,
86-
"Configure maximum burst sizes of the packet processing graph.",
105+
"Configure packet processing graph parameters.",
87106
with_help(
88107
"Maximum size of graph vectors.", ec_node_uint("VECTOR", 1, UINT16_MAX, 10)
89108
),
90109
with_help(
91110
"Maximum size of RX queue burst.", ec_node_uint("BURST", 1, UINT16_MAX, 10)
111+
),
112+
with_help(
113+
"ICMP errors/sec per node per worker (0 = no limit).",
114+
ec_node_uint("ICMP_ERROR_RATE", 0, UINT16_MAX, 10)
115+
),
116+
with_help(
117+
"ARP packets/sec per worker (0 = no limit).",
118+
ec_node_uint("ARP_RATE", 0, UINT16_MAX, 10)
119+
),
120+
with_help(
121+
"ICMP/ICMPv6 input packets/sec per worker (0 = no limit).",
122+
ec_node_uint("ICMP_RATE", 0, UINT16_MAX, 10)
92123
)
93124
);
94125
if (ret < 0)

modules/infra/control/graph.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,12 @@ static struct iface_info_port *find_port(vec struct iface_info_port **ports, uin
8585
return port;
8686
}
8787

88-
static struct gr_graph_conf graph_conf = {
88+
struct gr_graph_conf graph_conf = {
8989
.rx_burst_max = 64,
9090
.vector_max = 64,
91+
.icmp_error_rate = 1000,
92+
.arp_rate = 1000,
93+
.icmp_rate = 1000,
9194
};
9295

9396
static int
@@ -630,6 +633,12 @@ static struct api_out graph_conf_set(const void *request, struct api_ctx *) {
630633
return api_out(EOVERFLOW, 0, NULL);
631634
graph_conf.vector_max = req->vector_max;
632635
}
636+
if (req->set_attrs & GR_GRAPH_SET_ICMP_ERROR)
637+
graph_conf.icmp_error_rate = req->icmp_error_rate;
638+
if (req->set_attrs & GR_GRAPH_SET_ARP)
639+
graph_conf.arp_rate = req->arp_rate;
640+
if (req->set_attrs & GR_GRAPH_SET_ICMP)
641+
graph_conf.icmp_rate = req->icmp_rate;
633642

634643
if (graph_conf.rx_burst_max == prev.rx_burst_max
635644
&& graph_conf.vector_max == prev.vector_max)

modules/infra/control/graph.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ rte_node_enqueue_x1(struct rte_graph *, struct rte_node *, rte_edge_t next, void
3737

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

40+
extern struct gr_graph_conf graph_conf;
41+
4042
uint16_t drop_packets(struct rte_graph *, struct rte_node *, void **, uint16_t);
4143
int drop_format(char *buf, size_t buf_len, const void *data, size_t data_len);
4244

modules/infra/datapath/datapath.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,32 @@
33

44
#pragma once
55

6+
#include <rte_cycles.h>
7+
8+
#include <stdint.h>
9+
610
void *gr_datapath_loop(void *priv);
11+
12+
struct rate_limit_ctx {
13+
uint16_t tokens;
14+
uint64_t last_refill;
15+
} __attribute__((packed));
16+
17+
static inline bool
18+
rate_limited(struct rate_limit_ctx *ctx, const uint16_t max_rate, const uint16_t nb_pkts) {
19+
uint64_t now = rte_rdtsc();
20+
assert(ctx != NULL);
21+
22+
if (max_rate == 0)
23+
return false;
24+
25+
if (now - ctx->last_refill >= rte_get_tsc_hz()) {
26+
ctx->tokens = max_rate;
27+
ctx->last_refill = now;
28+
} else if (ctx->tokens == 0) {
29+
return true;
30+
}
31+
ctx->tokens -= RTE_MIN(ctx->tokens, nb_pkts);
32+
33+
return false;
34+
}

modules/ip/datapath/arp_input.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: BSD-3-Clause
22
// Copyright (c) 2024 Robin Jarry
33

4+
#include "datapath.h"
45
#include "eth.h"
56
#include "graph.h"
67
#include "mbuf.h"
@@ -9,20 +10,29 @@
910
#include <rte_arp.h>
1011
#include <rte_ether.h>
1112

13+
GR_NODE_CTX_TYPE(arp_input_ctx, { struct rate_limit_ctx limit; });
14+
1215
enum {
1316
OP_REQUEST = 0,
1417
OP_REPLY,
1518
OP_UNSUPP,
1619
PROTO_UNSUPP,
20+
RATE_LIMITED,
1721
EDGE_COUNT,
1822
};
1923

2024
static uint16_t
2125
arp_input_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) {
26+
struct arp_input_ctx *ctx = arp_input_ctx(node);
2227
struct rte_arp_hdr *arp, *t;
2328
struct rte_mbuf *mbuf;
2429
rte_edge_t edge;
2530

31+
if (rate_limited(&ctx->limit, graph_conf.arp_rate, nb_objs)) {
32+
rte_node_next_stream_move(graph, node, RATE_LIMITED);
33+
return nb_objs;
34+
}
35+
2636
for (uint16_t i = 0; i < nb_objs; i++) {
2737
mbuf = objs[i];
2838

@@ -58,6 +68,13 @@ arp_input_process(struct rte_graph *graph, struct rte_node *node, void **objs, u
5868
return nb_objs;
5969
}
6070

71+
static int arp_input_init(const struct rte_graph *, struct rte_node *node) {
72+
struct arp_input_ctx *ctx = arp_input_ctx(node);
73+
ctx->limit.tokens = graph_conf.arp_rate;
74+
ctx->limit.last_refill = rte_rdtsc();
75+
return 0;
76+
}
77+
6178
static void arp_input_register(void) {
6279
gr_eth_input_add_type(RTE_BE16(RTE_ETHER_TYPE_ARP), "arp_input");
6380
}
@@ -66,13 +83,15 @@ static struct rte_node_register node = {
6683
.name = "arp_input",
6784

6885
.process = arp_input_process,
86+
.init = arp_input_init,
6987

7088
.nb_edges = EDGE_COUNT,
7189
.next_nodes = {
7290
[OP_REQUEST] = "arp_input_request",
7391
[OP_REPLY] = "arp_input_reply",
7492
[OP_UNSUPP] = "arp_input_op_unsupp",
7593
[PROTO_UNSUPP] = "arp_input_proto_unsupp",
94+
[RATE_LIMITED] = "error_rate_limited",
7695
},
7796
};
7897

modules/ip/datapath/icmp_input.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Copyright (c) 2024 Robin Jarry
33

44
#include "control_output.h"
5+
#include "datapath.h"
56
#include "graph.h"
67
#include "ip4_datapath.h"
78
#include "log.h"
@@ -12,11 +13,14 @@
1213

1314
#include <rte_icmp.h>
1415

16+
GR_NODE_CTX_TYPE(icmp_input_ctx, { struct rate_limit_ctx limit; });
17+
1518
enum {
1619
OUTPUT = 0,
1720
CONTROL,
1821
INVALID,
1922
UNSUPPORTED,
23+
RATE_LIMITED,
2024
EDGE_COUNT,
2125
};
2226

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

2731
static uint16_t
2832
icmp_input_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) {
33+
struct icmp_input_ctx *ctx = icmp_input_ctx(node);
2934
struct ip_local_mbuf_data *ip_data;
3035
struct rte_icmp_hdr *icmp;
3136
struct rte_mbuf *mbuf;
3237
rte_edge_t edge;
3338
uint16_t cksum;
3439
ip4_addr_t ip;
3540

41+
if (rate_limited(&ctx->limit, graph_conf.icmp_rate, nb_objs)) {
42+
rte_node_next_stream_move(graph, node, RATE_LIMITED);
43+
return nb_objs;
44+
}
45+
3646
for (uint16_t i = 0; i < nb_objs; i++) {
3747
mbuf = objs[i];
3848
icmp = rte_pktmbuf_mtod(mbuf, struct rte_icmp_hdr *);
@@ -80,6 +90,13 @@ void icmp_input_register_callback(uint8_t icmp_type, control_queue_cb_t cb) {
8090
icmp_cb[icmp_type] = cb;
8191
}
8292

93+
static int icmp_input_init(const struct rte_graph *, struct rte_node *node) {
94+
struct icmp_input_ctx *ctx = icmp_input_ctx(node);
95+
ctx->limit.tokens = graph_conf.icmp_rate;
96+
ctx->limit.last_refill = rte_rdtsc();
97+
return 0;
98+
}
99+
83100
static void icmp_input_register(void) {
84101
ip_input_local_add_proto(IPPROTO_ICMP, "icmp_input");
85102
}
@@ -88,13 +105,15 @@ static struct rte_node_register icmp_input_node = {
88105
.name = "icmp_input",
89106

90107
.process = icmp_input_process,
108+
.init = icmp_input_init,
91109

92110
.nb_edges = EDGE_COUNT,
93111
.next_nodes = {
94112
[OUTPUT] = "icmp_output",
95113
[CONTROL] = "control_output",
96114
[INVALID] = "icmp_input_invalid",
97115
[UNSUPPORTED] = "icmp_input_unsupported",
116+
[RATE_LIMITED] = "error_rate_limited",
98117
},
99118
};
100119

modules/ip/datapath/ip_error.c

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: BSD-3-Clause
22
// Copyright (c) 2024 Christophe Fontaine
33

4+
#include "datapath.h"
45
#include "graph.h"
56
#include "ip4.h"
67
#include "ip4_datapath.h"
@@ -13,18 +14,20 @@
1314
GR_NODE_CTX_TYPE(ip_error_ctx, {
1415
uint8_t icmp_type;
1516
uint8_t icmp_code;
17+
struct rate_limit_ctx limit;
1618
});
1719

1820
enum edges {
1921
ICMP_OUTPUT = 0,
2022
NO_HEADROOM,
2123
NO_IP,
24+
RATE_LIMITED,
2225
EDGE_COUNT,
2326
};
2427

2528
static uint16_t
2629
ip_error_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) {
27-
const struct ip_error_ctx *ctx = ip_error_ctx(node);
30+
struct ip_error_ctx *ctx = ip_error_ctx(node);
2831
struct ip_local_mbuf_data *ip_data;
2932
const struct nexthop_info_l3 *l3;
3033
const struct nexthop *nh, *local;
@@ -36,6 +39,11 @@ ip_error_process(struct rte_graph *graph, struct rte_node *node, void **objs, ui
3639
rte_edge_t edge;
3740
unsigned len;
3841

42+
if (rate_limited(&ctx->limit, graph_conf.icmp_error_rate, nb_objs)) {
43+
rte_node_next_stream_move(graph, node, RATE_LIMITED);
44+
return nb_objs;
45+
}
46+
3947
for (uint16_t i = 0; i < nb_objs; i++) {
4048
mbuf = objs[i];
4149

@@ -100,20 +108,26 @@ static int ttl_exceeded_init(const struct rte_graph *, struct rte_node *node) {
100108
struct ip_error_ctx *ctx = ip_error_ctx(node);
101109
ctx->icmp_type = RTE_ICMP_TYPE_TTL_EXCEEDED;
102110
ctx->icmp_code = RTE_ICMP_CODE_TTL_EXCEEDED;
111+
ctx->limit.tokens = graph_conf.icmp_error_rate;
112+
ctx->limit.last_refill = rte_rdtsc();
103113
return 0;
104114
}
105115

106116
static int no_route_init(const struct rte_graph *, struct rte_node *node) {
107117
struct ip_error_ctx *ctx = ip_error_ctx(node);
108118
ctx->icmp_type = RTE_ICMP_TYPE_DEST_UNREACHABLE;
109119
ctx->icmp_code = RTE_ICMP_CODE_UNREACH_NET;
120+
ctx->limit.tokens = graph_conf.icmp_error_rate;
121+
ctx->limit.last_refill = rte_rdtsc();
110122
return 0;
111123
}
112124

113125
static int frag_needed_init(const struct rte_graph *, struct rte_node *node) {
114126
struct ip_error_ctx *ctx = ip_error_ctx(node);
115127
ctx->icmp_type = RTE_ICMP_TYPE_DEST_UNREACHABLE;
116128
ctx->icmp_code = RTE_ICMP_CODE_UNREACH_FRAG;
129+
ctx->limit.tokens = graph_conf.icmp_error_rate;
130+
ctx->limit.last_refill = rte_rdtsc();
117131
return 0;
118132
}
119133

@@ -125,6 +139,7 @@ static struct rte_node_register ip_forward_ttl_exceeded_node = {
125139
[ICMP_OUTPUT] = "icmp_output",
126140
[NO_HEADROOM] = "error_no_headroom",
127141
[NO_IP] = "error_no_local_ip",
142+
[RATE_LIMITED] = "error_rate_limited",
128143
},
129144
.init = ttl_exceeded_init,
130145
};
@@ -137,6 +152,7 @@ static struct rte_node_register no_route_node = {
137152
[ICMP_OUTPUT] = "icmp_output",
138153
[NO_HEADROOM] = "error_no_headroom",
139154
[NO_IP] = "error_no_local_ip",
155+
[RATE_LIMITED] = "error_rate_limited",
140156
},
141157
.init = no_route_init,
142158
};
@@ -149,6 +165,7 @@ static struct rte_node_register frag_needed_node = {
149165
[ICMP_OUTPUT] = "icmp_output",
150166
[NO_HEADROOM] = "error_no_headroom",
151167
[NO_IP] = "error_no_local_ip",
168+
[RATE_LIMITED] = "error_rate_limited",
152169
},
153170
.init = frag_needed_init,
154171
};
@@ -173,3 +190,4 @@ GR_NODE_REGISTER(info_no_route);
173190
GR_NODE_REGISTER(info_frag_needed);
174191

175192
GR_DROP_REGISTER(error_no_local_ip);
193+
GR_DROP_REGISTER(error_rate_limited);

0 commit comments

Comments
 (0)