Skip to content

Commit 0a74d24

Browse files
committed
iface: add bonding support with active-backup mode
Allow aggregating multiple physical ports into a single logical bond interface. Only active-backup mode is implemented where traffic flows through a single active member port at any time. Active member selection prioritizes the configured primary port when it is UP and RUNNING. Otherwise, the first available UP and RUNNING member becomes active. The bond subscribes to port link state events and automatically fails over when necessary. In the RX path, port_rx checks if a port belongs to a bond by looking at the bond_iface_id field. Packets from inactive members are dropped immediately. Packets from the active member get their iface pointer rewritten to point to the bond interface before being forwarded to normal processing. In the TX path, bond_output examines the current active member and rewrites the mbuf iface pointer and port fields before forwarding to port_output. Interface configuration (MAC address, MTU, promisc, allmulti, VLANs) is propagated from the bond to all member ports. MAC addresses are tracked separately to handle members being added or removed dynamically. This ensures that bond reconfiguration does not leave stale addresses on removed members or forget to apply addresses to newly added ones. New CLI commands allow creating and managing bond interfaces: grcli interface add bond NAME mode active-backup member PORT ... grcli interface set bond NAME member PORT ... primary PORT The smoke test creates two bond interfaces with multiple member ports connected via the Linux bridge to network namespaces. It verifies bidirectional connectivity and tests failover by bringing down the active member port and confirming traffic continues through the backup. Signed-off-by: Robin Jarry <[email protected]> Reviewed-by: Christophe Fontaine <[email protected]>
1 parent 5fc9edc commit 0a74d24

File tree

14 files changed

+1457
-488
lines changed

14 files changed

+1457
-488
lines changed

docs/graph.svg

Lines changed: 493 additions & 475 deletions
Loading

modules/infra/api/gr_infra.h

Lines changed: 38 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

@@ -92,6 +93,7 @@ struct __gr_iface_info_port_base {
9293
uint16_t n_txq;
9394
uint16_t rxq_size;
9495
uint16_t txq_size;
96+
uint16_t bond_iface_id;
9597
uint32_t link_speed; //!< Physical link speed in Megabit/sec.
9698
struct rte_ether_addr mac;
9799
};
@@ -117,6 +119,40 @@ struct gr_iface_info_vlan {
117119
struct rte_ether_addr mac;
118120
};
119121

122+
// Bond operational modes
123+
typedef enum : uint8_t {
124+
GR_BOND_MODE_ACTIVE_BACKUP = 1,
125+
} gr_bond_mode_t;
126+
127+
static inline char *gr_bond_mode_name(gr_bond_mode_t mode) {
128+
switch (mode) {
129+
case GR_BOND_MODE_ACTIVE_BACKUP:
130+
return "active-backup";
131+
}
132+
return "?";
133+
}
134+
135+
// Bond reconfig attributes
136+
#define GR_BOND_SET_MODE GR_BIT64(32)
137+
#define GR_BOND_SET_MEMBERS GR_BIT64(33)
138+
#define GR_BOND_SET_PRIMARY GR_BIT64(34)
139+
#define GR_BOND_SET_MAC GR_BIT64(35)
140+
141+
struct gr_bond_member {
142+
uint16_t iface_id;
143+
bool active;
144+
};
145+
146+
// Info for GR_IFACE_TYPE_BOND interfaces
147+
struct gr_iface_info_bond {
148+
gr_bond_mode_t mode;
149+
struct rte_ether_addr mac;
150+
151+
uint8_t primary_member;
152+
uint8_t n_members;
153+
struct gr_bond_member members[8];
154+
};
155+
120156
struct gr_port_rxq_map {
121157
uint16_t iface_id;
122158
uint16_t rxq_id;
@@ -333,6 +369,8 @@ static inline const char *gr_iface_type_name(gr_iface_type_t type) {
333369
return "vlan";
334370
case GR_IFACE_TYPE_IPIP:
335371
return "ipip";
372+
case GR_IFACE_TYPE_BOND:
373+
return "bond";
336374
case GR_IFACE_TYPE_COUNT:
337375
break;
338376
}

modules/infra/cli/bond.c

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

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',

modules/infra/cli/port.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ static void port_show(struct gr_api_client *c, const struct gr_iface *iface) {
2020
printf("devargs: %s\n", port->devargs);
2121
printf("driver: %s\n", port->driver_name);
2222
printf("mac: " ETH_F "\n", &port->mac);
23+
if (port->bond_iface_id != GR_IFACE_ID_UNDEF) {
24+
struct gr_iface *bond = iface_from_id(c, port->bond_iface_id);
25+
if (bond != NULL)
26+
printf("bond: %s\n", bond->name);
27+
else
28+
printf("bond: %u\n", port->bond_iface_id);
29+
free(bond);
30+
}
2331
if (port->link_speed == UINT32_MAX)
2432
printf("speed: unknown\n");
2533
else
@@ -42,7 +50,7 @@ static void port_show(struct gr_api_client *c, const struct gr_iface *iface) {
4250
static void
4351
port_list_info(struct gr_api_client *c, const struct gr_iface *iface, char *buf, size_t len) {
4452
const struct gr_iface_info_port *port = (const struct gr_iface_info_port *)iface->info;
45-
struct gr_iface *peer = NULL;
53+
struct gr_iface *peer = NULL, *bond = NULL;
4654
size_t n = 0;
4755

4856
SAFE_BUF(snprintf, len, "devargs=%s mac=" ETH_F, port->devargs, &port->mac);
@@ -52,9 +60,15 @@ port_list_info(struct gr_api_client *c, const struct gr_iface *iface, char *buf,
5260
else
5361
SAFE_BUF(snprintf, len, " xc_peer=%u", iface->domain_id);
5462
}
63+
if (port->bond_iface_id != GR_IFACE_ID_UNDEF) {
64+
if ((bond = iface_from_id(c, port->bond_iface_id)) != NULL)
65+
SAFE_BUF(snprintf, len, " bond=%s", bond->name);
66+
else
67+
SAFE_BUF(snprintf, len, " bond=%u", port->bond_iface_id);
68+
}
5569
err:
5670
free(peer);
57-
return;
71+
free(bond);
5872
}
5973

6074
static struct cli_iface_type port_type = {

0 commit comments

Comments
 (0)