Skip to content

Commit e9a4647

Browse files
committed
iface: add bonding support with active-backup mode
Add support for bonding multiple physical ports into a single logical interface. This initial implementation supports active-backup mode where only one member port is active at a time for both RX and TX traffic. The bond interface type can aggregate multiple physical ports. Member ports forward their received packets to the bond interface when active. The bond output node selects the active member based on the configured mode and forwards packets accordingly. Active member selection in active-backup mode works as follows: - The primary member is preferred if it is UP and RUNNING - If the primary is down, the first available UP and RUNNING member becomes active - When member link states change, the bond automatically fails over to another member if needed The bond interface propagates configuration to all members: - MAC addresses (both primary and additional addresses) - MTU settings - Interface flags (promisc, allmulti, up/down) - VLAN memberships A new ACTIVE state flag indicates which member is currently handling traffic. Port interfaces track their bond membership via bond_iface_id. The RX datapath checks if incoming ports are bond members. If the member is inactive or not UP, packets are dropped. Active member packets are forwarded with the iface pointer updated to the bond interface. The TX datapath uses bond_output node to select the active member and update the mbuf iface pointer before forwarding to port_output. Add CLI commands to manage bond interfaces. Signed-off-by: Robin Jarry <[email protected]>
1 parent d3bab20 commit e9a4647

File tree

14 files changed

+1393
-441
lines changed

14 files changed

+1393
-441
lines changed

docs/graph.svg

Lines changed: 450 additions & 432 deletions
Loading

modules/infra/api/gr_infra.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ typedef enum : uint8_t {
1919
GR_IFACE_TYPE_PORT,
2020
GR_IFACE_TYPE_VLAN,
2121
GR_IFACE_TYPE_IPIP,
22+
GR_IFACE_TYPE_BOND,
2223
GR_IFACE_TYPE_COUNT
2324
} gr_iface_type_t;
2425

@@ -35,6 +36,7 @@ typedef enum : uint16_t {
3536
// Interface state flags
3637
typedef enum : uint16_t {
3738
GR_IFACE_S_RUNNING = GR_BIT16(0),
39+
GR_IFACE_S_ACTIVE = GR_BIT16(1), // Used for bond members
3840
} gr_iface_state_t;
3941

4042
// Interface reconfig attributes
@@ -90,6 +92,7 @@ struct __gr_iface_info_port_base {
9092
uint16_t n_txq;
9193
uint16_t rxq_size;
9294
uint16_t txq_size;
95+
uint16_t bond_iface_id;
9396
uint32_t link_speed; //!< Physical link speed in Megabit/sec.
9497
struct rte_ether_addr mac;
9598
};
@@ -115,6 +118,36 @@ struct gr_iface_info_vlan {
115118
struct rte_ether_addr mac;
116119
};
117120

121+
// Bond operational modes
122+
typedef enum : uint8_t {
123+
GR_BOND_MODE_ACTIVE_BACKUP = 1,
124+
} gr_bond_mode_t;
125+
126+
static inline char *gr_bond_mode_name(gr_bond_mode_t mode) {
127+
switch (mode) {
128+
case GR_BOND_MODE_ACTIVE_BACKUP:
129+
return "active-backup";
130+
}
131+
return "?";
132+
}
133+
134+
// Bond reconfig attributes
135+
#define GR_BOND_SET_MODE GR_BIT64(32)
136+
#define GR_BOND_SET_MEMBERS GR_BIT64(33)
137+
#define GR_BOND_SET_PRIMARY GR_BIT64(34)
138+
#define GR_BOND_SET_MAC GR_BIT64(35)
139+
140+
// Info for GR_IFACE_TYPE_BOND interfaces
141+
struct gr_iface_info_bond {
142+
gr_bond_mode_t mode; // Bond operational mode
143+
struct rte_ether_addr mac; // Bond interface MAC address
144+
145+
// Member port information
146+
uint8_t primary_member; // Primary port ID (for active-backup mode)
147+
uint8_t n_members; // Number of member ports
148+
uint16_t member_iface_ids[8];
149+
};
150+
118151
struct gr_port_rxq_map {
119152
uint16_t iface_id;
120153
uint16_t rxq_id;
@@ -331,6 +364,8 @@ static inline const char *gr_iface_type_name(gr_iface_type_t type) {
331364
return "vlan";
332365
case GR_IFACE_TYPE_IPIP:
333366
return "ipip";
367+
case GR_IFACE_TYPE_BOND:
368+
return "bond";
334369
case GR_IFACE_TYPE_COUNT:
335370
break;
336371
}

modules/infra/cli/bond.c

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
// Copyright (c) 2025 Robin Jarry
3+
4+
#include <gr_api.h>
5+
#include <gr_cli.h>
6+
#include <gr_cli_iface.h>
7+
#include <gr_infra.h>
8+
#include <gr_net_types.h>
9+
#include <gr_table.h>
10+
11+
#include <ecoli.h>
12+
#include <libsmartcols.h>
13+
14+
#include <errno.h>
15+
#include <sys/queue.h>
16+
17+
static int bond_mode_from_str(const char *str, gr_bond_mode_t *mode) {
18+
if (strcmp(str, "active-backup") == 0) {
19+
*mode = GR_BOND_MODE_ACTIVE_BACKUP;
20+
return 0;
21+
}
22+
return errno_set(EINVAL);
23+
}
24+
25+
static void bond_show(struct gr_api_client *c, const struct gr_iface *iface) {
26+
const struct gr_iface_info_bond *bond = PAYLOAD(iface);
27+
28+
printf("mode: %s\n", gr_bond_mode_name(bond->mode));
29+
printf("mac: " ETH_F "\n", &bond->mac);
30+
printf("members:\n");
31+
for (uint8_t m = 0; m < bond->n_members; m++) {
32+
struct gr_iface *member = iface_from_id(c, bond->member_iface_ids[m]);
33+
if (member == NULL)
34+
continue;
35+
36+
printf(" - name: %s\n", member->name);
37+
printf(" active: %s\n", (member->state & GR_IFACE_S_ACTIVE) ? "yes" : "no");
38+
if (bond->mode == GR_BOND_MODE_ACTIVE_BACKUP && m == bond->primary_member)
39+
printf(" primary: true\n");
40+
if (member->type == GR_IFACE_TYPE_PORT) {
41+
const struct gr_iface_info_port *port;
42+
port = (const struct gr_iface_info_port *)member->info;
43+
printf(" mac: " ETH_F "\n", &port->mac);
44+
if (port->link_speed == UINT32_MAX)
45+
printf(" speed: unknown\n");
46+
else
47+
printf(" speed: %u Mb/s\n", port->link_speed);
48+
}
49+
50+
free(member);
51+
}
52+
}
53+
54+
static void
55+
bond_list_info(struct gr_api_client *c, const struct gr_iface *iface, char *buf, size_t len) {
56+
const struct gr_iface_info_bond *bond = PAYLOAD(iface);
57+
struct gr_iface *i = NULL;
58+
uint16_t member_iface_id;
59+
size_t n = 0;
60+
61+
errno = 0;
62+
63+
SAFE_BUF(
64+
snprintf,
65+
len,
66+
"mode=%s mac=" ETH_F " members=%u",
67+
gr_bond_mode_name(bond->mode),
68+
&bond->mac,
69+
bond->n_members
70+
);
71+
72+
switch (bond->mode) {
73+
case GR_BOND_MODE_ACTIVE_BACKUP:
74+
member_iface_id = bond->member_iface_ids[bond->primary_member];
75+
if ((i = iface_from_id(c, member_iface_id)) == NULL)
76+
SAFE_BUF(snprintf, len, " primary=%u", member_iface_id);
77+
else
78+
SAFE_BUF(snprintf, len, " primary=%s", i->name);
79+
break;
80+
}
81+
82+
err:
83+
free(i);
84+
}
85+
86+
static struct cli_iface_type bond_type = {
87+
.type_id = GR_IFACE_TYPE_BOND,
88+
.name = "bond",
89+
.show = bond_show,
90+
.list_info = bond_list_info,
91+
};
92+
93+
static uint64_t parse_bond_args(
94+
struct gr_api_client *c,
95+
const struct ec_pnode *p,
96+
struct gr_iface *iface,
97+
bool update
98+
) {
99+
struct gr_iface_info_bond *bond = (struct gr_iface_info_bond *)iface->info;
100+
uint64_t set_attrs;
101+
const char *str;
102+
103+
set_attrs = parse_iface_args(c, p, iface, sizeof(*bond), update);
104+
105+
if ((str = arg_str(p, "MODE")) != NULL) {
106+
if (bond_mode_from_str(str, &bond->mode) < 0)
107+
goto err;
108+
set_attrs |= GR_BOND_SET_MODE;
109+
}
110+
111+
if (arg_str(p, "MEMBER") != NULL) {
112+
const struct ec_pnode *m = NULL;
113+
bond->n_members = 0;
114+
while ((m = ec_pnode_find_next(p, m, "MEMBER", true)) != NULL) {
115+
if (bond->n_members >= ARRAY_DIM(bond->member_iface_ids)) {
116+
errno = EUSERS;
117+
goto err;
118+
}
119+
const struct ec_strvec *v = ec_pnode_get_strvec(m);
120+
assert(v != NULL);
121+
assert(ec_strvec_len(v) == 1);
122+
struct gr_iface *member = iface_from_name(c, ec_strvec_val(v, 0));
123+
if (member == NULL) {
124+
goto err;
125+
}
126+
bond->member_iface_ids[bond->n_members++] = member->id;
127+
free(member);
128+
}
129+
set_attrs |= GR_BOND_SET_MEMBERS;
130+
}
131+
132+
if ((str = arg_str(p, "PRIMARY")) != NULL) {
133+
struct gr_iface *primary = iface_from_name(c, str);
134+
if (primary == NULL)
135+
goto err;
136+
137+
uint8_t primary_id = UINT8_MAX;
138+
for (uint8_t i = 0; i < bond->n_members; i++) {
139+
if (bond->member_iface_ids[i] == primary->id) {
140+
primary_id = i;
141+
break;
142+
}
143+
}
144+
free(primary);
145+
if (primary_id == UINT8_MAX) {
146+
errno = ENOLINK;
147+
goto err;
148+
}
149+
bond->primary_member = primary_id;
150+
set_attrs |= GR_BOND_SET_MODE;
151+
}
152+
153+
if (arg_eth_addr(p, "MAC", &bond->mac) == 0)
154+
set_attrs |= GR_BOND_SET_MAC;
155+
156+
if (set_attrs == 0)
157+
errno = EINVAL;
158+
159+
return set_attrs;
160+
err:
161+
return 0;
162+
}
163+
164+
static cmd_status_t bond_add(struct gr_api_client *c, const struct ec_pnode *p) {
165+
const struct gr_infra_iface_add_resp *resp;
166+
struct gr_infra_iface_add_req *req = NULL;
167+
void *resp_ptr = NULL;
168+
size_t len;
169+
170+
len = sizeof(*req) + sizeof(struct gr_iface_info_bond);
171+
if ((req = calloc(1, len)) == NULL)
172+
goto err;
173+
174+
req->iface.type = GR_IFACE_TYPE_BOND;
175+
req->iface.flags = GR_IFACE_F_UP;
176+
177+
if (parse_bond_args(c, p, &req->iface, false) == 0)
178+
goto err;
179+
180+
if (gr_api_client_send_recv(c, GR_INFRA_IFACE_ADD, len, req, &resp_ptr) < 0)
181+
goto err;
182+
183+
free(req);
184+
resp = resp_ptr;
185+
printf("Created interface %u\n", resp->iface_id);
186+
free(resp_ptr);
187+
return CMD_SUCCESS;
188+
err:
189+
free(req);
190+
return CMD_ERROR;
191+
}
192+
193+
static cmd_status_t bond_set(struct gr_api_client *c, const struct ec_pnode *p) {
194+
struct gr_infra_iface_set_req *req = NULL;
195+
cmd_status_t ret = CMD_ERROR;
196+
size_t len;
197+
198+
len = sizeof(*req) + sizeof(struct gr_iface_info_bond);
199+
if ((req = calloc(1, len)) == NULL)
200+
goto out;
201+
202+
if ((req->set_attrs = parse_bond_args(c, p, &req->iface, true)) == 0)
203+
goto out;
204+
205+
if (gr_api_client_send_recv(c, GR_INFRA_IFACE_SET, len, req, NULL) < 0)
206+
goto out;
207+
208+
ret = CMD_SUCCESS;
209+
out:
210+
free(req);
211+
return ret;
212+
}
213+
214+
#define BOND_ATTRS_CMD "(primary PRIMARY),(mac MAC)"
215+
#define BOND_ATTRS_ARGS \
216+
IFACE_ATTRS_ARGS, \
217+
with_help( \
218+
"Bond mode.", \
219+
EC_NODE_OR( \
220+
"MODE", \
221+
with_help("Active backup mode.", ec_node_str("", "active-backup")) \
222+
) \
223+
), \
224+
with_help( \
225+
"Primary member.", \
226+
ec_node_dyn("PRIMARY", complete_iface_names, INT2PTR(GR_IFACE_TYPE_PORT)) \
227+
), \
228+
with_help("Set the bond MAC address.", ec_node_re("MAC", ETH_ADDR_RE))
229+
230+
static int ctx_init(struct ec_node *root) {
231+
int ret;
232+
233+
ret = CLI_COMMAND(
234+
INTERFACE_ADD_CTX(root),
235+
"bond NAME mode MODE (member MEMBER)+ [" BOND_ATTRS_CMD "]",
236+
bond_add,
237+
"Create a new bond interface.",
238+
with_help("Interface name.", ec_node("any", "NAME")),
239+
with_help(
240+
"Member port interface.",
241+
ec_node_dyn("MEMBER", complete_iface_names, INT2PTR(GR_IFACE_TYPE_PORT))
242+
),
243+
BOND_ATTRS_ARGS
244+
);
245+
if (ret < 0)
246+
return ret;
247+
ret = CLI_COMMAND(
248+
INTERFACE_SET_CTX(root),
249+
"bond NAME (name NEW_NAME),(member MEMBER)+,(mode MODE)," BOND_ATTRS_CMD,
250+
bond_set,
251+
"Modify bond parameters.",
252+
with_help(
253+
"Interface name.",
254+
ec_node_dyn("NAME", complete_iface_names, INT2PTR(GR_IFACE_TYPE_BOND))
255+
),
256+
with_help("New interface name.", ec_node("any", "NEW_NAME")),
257+
with_help(
258+
"Member port interface.",
259+
ec_node_dyn("MEMBER", complete_iface_names, INT2PTR(GR_IFACE_TYPE_PORT))
260+
),
261+
with_help(
262+
"Primary member.",
263+
ec_node_dyn("PRIMARY", complete_iface_names, INT2PTR(GR_IFACE_TYPE_PORT))
264+
),
265+
BOND_ATTRS_ARGS
266+
);
267+
if (ret < 0)
268+
return ret;
269+
270+
return 0;
271+
}
272+
273+
static struct cli_context ctx = {
274+
.name = "infra bond",
275+
.init = ctx_init,
276+
};
277+
278+
static void __attribute__((constructor, used)) init(void) {
279+
cli_context_register(&ctx);
280+
register_iface_type(&bond_type);
281+
}

modules/infra/cli/iface.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ static cmd_status_t iface_list(struct gr_api_client *c, const struct ec_pnode *p
236236
SAFE_BUF(snprintf, sizeof(buf), "down");
237237
if (iface->state & GR_IFACE_S_RUNNING)
238238
SAFE_BUF(snprintf, sizeof(buf), " running");
239+
if (iface->state & GR_IFACE_S_ACTIVE)
240+
SAFE_BUF(snprintf, sizeof(buf), " active");
239241
if (iface->flags & GR_IFACE_F_PROMISC)
240242
SAFE_BUF(snprintf, sizeof(buf), " promisc");
241243
if (iface->flags & GR_IFACE_F_ALLMULTI)

modules/infra/cli/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
cli_src += files(
55
'address.c',
66
'affinity.c',
7+
'bond.c',
78
'events.c',
89
'graph.c',
910
'iface.c',

0 commit comments

Comments
 (0)