Skip to content

Commit 204876f

Browse files
infra: add nexthop group
Add nexthop type "GROUP", which is an aggregation of multiple nexthops. This may be used for ECMP, in order to configure up to GR_NEXTHOP_GROUP_MAX nexthop for a route. In a group, nexthop are added with a relative weight: grout# nexthop add group id 333 members nh 45 weight 3 nh 42 grout# nexthop type group VRF ID ORIGIN IFACE TYPE INFO 0 333 user group id(45/3) id(42/1) Signed-off-by: Christophe Fontaine <[email protected]>
1 parent 119b782 commit 204876f

File tree

7 files changed

+539
-285
lines changed

7 files changed

+539
-285
lines changed

docs/graph.svg

Lines changed: 285 additions & 285 deletions
Loading

modules/infra/api/gr_nexthop.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ typedef enum : uint8_t {
3232
GR_NH_T_DNAT,
3333
GR_NH_T_BLACKHOLE,
3434
GR_NH_T_REJECT,
35+
GR_NH_T_GROUP, // ECMP
3536
#define GR_NH_T_ALL UINT8_C(0xff)
3637
} gr_nh_type_t;
3738

@@ -99,6 +100,17 @@ struct gr_nexthop_info_l3 {
99100
struct rte_ether_addr mac; //!< link-layer address
100101
};
101102

103+
// Info for GR_NH_T_GROUP nexthops
104+
struct gr_nexthop_group_member {
105+
uint32_t nh_id;
106+
uint32_t weight;
107+
};
108+
109+
struct gr_nexthop_info_group {
110+
uint32_t n_members;
111+
struct gr_nexthop_group_member members[];
112+
};
113+
102114
//! Nexthop structure exposed to the API.
103115
struct gr_nexthop {
104116
BASE(gr_nexthop_base);
@@ -166,6 +178,8 @@ static inline const char *gr_nh_type_name(const gr_nh_type_t type) {
166178
return "blackhole";
167179
case GR_NH_T_REJECT:
168180
return "reject";
181+
case GR_NH_T_GROUP:
182+
return "group";
169183
}
170184
return "?";
171185
}

modules/infra/cli/gr_cli_iface.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#pragma once
55

66
#include <gr_infra.h>
7+
#include <gr_nexthop.h>
78

89
#include <ecoli.h>
910

modules/infra/cli/nexthop.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,19 @@ static ssize_t format_nexthop_info_void(char *, size_t, const void *) {
111111
return 0;
112112
}
113113

114+
static ssize_t format_nexthop_info_group(char *buf, size_t len, const void *info) {
115+
const struct gr_nexthop_info_group *grp = info;
116+
ssize_t n = 0;
117+
118+
for (uint32_t i = 0; i < grp->n_members; i++)
119+
SAFE_BUF(
120+
snprintf, len, "id(%d/%d) ", grp->members[i].nh_id, grp->members[i].weight
121+
);
122+
return n;
123+
err:
124+
return -errno;
125+
}
126+
114127
static struct cli_nexthop_formatter blackhole_formatter = {
115128
.name = "blackhole",
116129
.type = GR_NH_T_BLACKHOLE,
@@ -123,6 +136,12 @@ static struct cli_nexthop_formatter reject_formatter = {
123136
.format = format_nexthop_info_void,
124137
};
125138

139+
static struct cli_nexthop_formatter group_formatter = {
140+
.name = "group",
141+
.type = GR_NH_T_GROUP,
142+
.format = format_nexthop_info_group,
143+
};
144+
126145
static int complete_nh_types(
127146
struct gr_api_client *,
128147
const struct ec_node *node,
@@ -284,6 +303,48 @@ static cmd_status_t nh_del(struct gr_api_client *c, const struct ec_pnode *p) {
284303
return CMD_SUCCESS;
285304
}
286305

306+
static cmd_status_t nh_group_add(struct gr_api_client *c, const struct ec_pnode *p) {
307+
const struct ec_pnode *n = ec_pnode_find(p, "MEMBERS");
308+
struct gr_nexthop_info_group *group;
309+
struct gr_nh_add_req *req = NULL;
310+
cmd_status_t ret = CMD_ERROR;
311+
size_t len;
312+
313+
len = sizeof(*req) + sizeof(*group) + ec_pnode_len(n) * sizeof(group->members[0]);
314+
if ((req = calloc(1, len)) == NULL)
315+
goto out;
316+
317+
req->exist_ok = true;
318+
req->nh.type = GR_NH_T_GROUP;
319+
req->nh.origin = GR_NH_ORIGIN_USER;
320+
321+
if (arg_u32(p, "ID", &req->nh.nh_id) < 0 && errno != ENOENT)
322+
goto out;
323+
324+
group = (struct gr_nexthop_info_group *)req->nh.info;
325+
326+
while (n) {
327+
n = ec_pnode_get_first_child(n);
328+
if (arg_u32(n, "NHID", &group->members[group->n_members].nh_id) < 0)
329+
goto out;
330+
if (arg_u32(n, "WEIGHT", &group->members[group->n_members].weight) < 0) {
331+
if (errno == ENOENT)
332+
group->members[group->n_members].weight = 1;
333+
else
334+
goto out;
335+
}
336+
group->n_members++;
337+
n = ec_pnode_next(n);
338+
}
339+
340+
if (gr_api_client_send_recv(c, GR_NH_ADD, len, req, NULL) < 0)
341+
goto out;
342+
ret = CMD_SUCCESS;
343+
out:
344+
free(req);
345+
return ret;
346+
}
347+
287348
static cmd_status_t nh_list(struct gr_api_client *c, const struct ec_pnode *p) {
288349
struct gr_nh_list_req req = {.vrf_id = GR_VRF_ID_ALL, .type = GR_NH_T_ALL};
289350
const struct gr_nexthop *nh;
@@ -414,6 +475,29 @@ static int ctx_init(struct ec_node *root) {
414475
with_help("Blackhole nexthop.", ec_node_str("blackhole", "blackhole")),
415476
with_help("Reject nexthop sending ICMP UNREACH.", ec_node_str("reject", "reject"))
416477
);
478+
if (ret < 0)
479+
return ret;
480+
ret = CLI_COMMAND(
481+
NEXTHOP_ADD_CTX(root),
482+
"group [(id ID)] members MEMBERS",
483+
nh_group_add,
484+
"Add a new nexthop group.",
485+
with_help("Nexthop ID.", ec_node_uint("ID", 1, UINT32_MAX - 1, 10)),
486+
with_help(
487+
"Nexthop member IDs with relative weights.",
488+
ec_node_many(
489+
"MEMBERS",
490+
EC_NODE_CMD(
491+
EC_NO_ID,
492+
"nh NHID [weight WEIGHT]",
493+
ec_node_uint("NHID", 1, UINT32_MAX - 1, 10),
494+
ec_node_uint("WEIGHT", 1, UINT32_MAX - 1, 10)
495+
),
496+
1,
497+
128
498+
)
499+
)
500+
);
417501
if (ret < 0)
418502
return ret;
419503
ret = CLI_COMMAND(
@@ -488,4 +572,5 @@ static void __attribute__((constructor, used)) init(void) {
488572
cli_nexthop_formatter_register(&l3_formatter);
489573
cli_nexthop_formatter_register(&blackhole_formatter);
490574
cli_nexthop_formatter_register(&reject_formatter);
575+
cli_nexthop_formatter_register(&group_formatter);
491576
}

modules/infra/control/gr_nh_control.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ struct hoplist {
5454
gr_vec struct nexthop **nh;
5555
};
5656

57+
struct nh_group_member {
58+
struct nexthop *nh;
59+
uint32_t weight;
60+
};
61+
62+
GR_NH_TYPE_INFO(GR_NH_T_GROUP, nexthop_info_group, {
63+
uint32_t n_members;
64+
gr_vec struct nh_group_member *members;
65+
});
66+
5767
// Lookup a nexthop from the global pool that matches the specified criteria.
5868
struct nexthop *
5969
nexthop_lookup(addr_family_t af, uint16_t vrf_id, uint16_t iface_id, const void *addr);

modules/infra/control/nexthop.c

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ static struct rte_mempool *pool;
3030
static struct gr_id_pool *pool_id;
3131
static struct rte_hash *hash_by_addr;
3232
static struct rte_hash *hash_by_id;
33+
gr_vec static struct nexthop **groups = NULL;
3334
static struct event *ageing_timer;
3435
static const struct nexthop_af_ops *af_ops[256];
3536
static const struct nexthop_type_ops *type_ops[256];
@@ -313,6 +314,7 @@ void nexthop_type_ops_register(gr_nh_type_t type, const struct nexthop_type_ops
313314
case GR_NH_T_DNAT:
314315
case GR_NH_T_BLACKHOLE:
315316
case GR_NH_T_REJECT:
317+
case GR_NH_T_GROUP:
316318
if (ops == NULL)
317319
ABORT("invalid type ops");
318320
if (type_ops[type] != NULL)
@@ -335,6 +337,7 @@ struct nexthop *nexthop_new(const struct gr_nexthop_base *base, const void *info
335337
case GR_NH_T_DNAT:
336338
case GR_NH_T_BLACKHOLE:
337339
case GR_NH_T_REJECT:
340+
case GR_NH_T_GROUP:
338341
break;
339342
default:
340343
ABORT("invalid nexthop type %hhu", base->type);
@@ -483,8 +486,25 @@ struct nexthop *nexthop_lookup_by_id(uint32_t nh_id) {
483486
return data;
484487
}
485488

489+
static void nh_groups_remove_member(const struct nexthop *nh) {
490+
struct nexthop_info_group *info;
491+
struct nexthop *group;
492+
493+
gr_vec_foreach (group, groups) {
494+
info = nexthop_info_group(group);
495+
for (uint32_t i = 0; i < info->n_members; i++) {
496+
if (info->members[i].nh == nh) {
497+
info->members[i].nh = info->members[info->n_members - 1].nh;
498+
info->members[i].weight = info->members[info->n_members - 1].weight;
499+
info->n_members--;
500+
}
501+
}
502+
}
503+
}
504+
486505
void nexthop_routes_cleanup(struct nexthop *nh) {
487506
const struct nexthop_af_ops *ops;
507+
nh_groups_remove_member(nh);
488508
for (unsigned i = 0; i < ARRAY_DIM(af_ops); i++) {
489509
ops = af_ops[i];
490510
if (ops != NULL)
@@ -495,6 +515,7 @@ void nexthop_routes_cleanup(struct nexthop *nh) {
495515
static void nh_cleanup_interface_cb(struct nexthop *nh, void *priv) {
496516
struct lookup_filter *filter = priv;
497517
if (nh->iface_id == filter->iface_id) {
518+
nh_groups_remove_member(nh);
498519
nexthop_routes_cleanup(nh);
499520
while (nh->ref_count)
500521
nexthop_decref(nh);
@@ -624,6 +645,7 @@ static void nh_init(struct event_base *ev_base) {
624645
}
625646

626647
static void nh_fini(struct event_base *) {
648+
gr_vec_free(groups);
627649
rte_hash_free(hash_by_addr);
628650
rte_hash_free(hash_by_id);
629651
if (ageing_timer)
@@ -795,11 +817,130 @@ static struct nexthop_type_ops l3_nh_ops = {
795817
.to_api = l3_to_api,
796818
};
797819

820+
static bool group_equal(const struct nexthop *a, const struct nexthop *b) {
821+
const struct nexthop_info_group *da = nexthop_info_group(a);
822+
const struct nexthop_info_group *db = nexthop_info_group(b);
823+
824+
if (da->n_members != db->n_members)
825+
return false;
826+
for (uint32_t i = 0; i < da->n_members; i++)
827+
if (da->members[i].nh != db->members[i].nh
828+
|| da->members[i].weight != db->members[i].weight)
829+
return false;
830+
return true;
831+
}
832+
833+
static void group_list_add(struct nexthop *nh) {
834+
struct nexthop *group;
835+
bool found = false;
836+
837+
gr_vec_foreach (group, groups) {
838+
if (group == nh)
839+
found = true;
840+
}
841+
842+
if (!found)
843+
gr_vec_add(groups, nh);
844+
}
845+
846+
static void group_list_del(const struct nexthop *nh) {
847+
struct nexthop *group;
848+
gr_vec_foreach (group, groups) {
849+
if (group == nh)
850+
gr_vec_del_swap(groups, __i);
851+
}
852+
}
853+
854+
static void group_free(struct nexthop *nh) {
855+
struct nexthop_info_group *pvt = nexthop_info_group(nh);
856+
857+
for (uint32_t i = 0; i < pvt->n_members; i++)
858+
nexthop_decref(pvt->members[i].nh);
859+
rte_free(pvt->members);
860+
group_list_del(nh);
861+
}
862+
863+
static int group_import_info(struct nexthop *nh, const void *info) {
864+
struct nexthop_info_group *pvt = nexthop_info_group(nh);
865+
const struct gr_nexthop_info_group *group = info;
866+
struct nh_group_member *members, *tmp;
867+
uint32_t count;
868+
869+
members = rte_zmalloc(
870+
__func__,
871+
sizeof(*pvt) + group->n_members * sizeof(pvt->members[0]),
872+
RTE_CACHE_LINE_SIZE
873+
);
874+
if (members == NULL)
875+
return errno_set(ENOMEM);
876+
877+
for (uint32_t i = 0; i < group->n_members; i++) {
878+
struct nexthop *nh = nexthop_lookup_by_id(group->members[i].nh_id);
879+
if (nh) {
880+
members[i].nh = nh;
881+
members[i].weight = group->members[i].weight;
882+
} else {
883+
rte_free(members);
884+
return errno_set(ENOENT);
885+
}
886+
}
887+
888+
for (uint32_t i = 0; i < group->n_members; i++)
889+
nexthop_incref(members[i].nh);
890+
891+
count = pvt->n_members;
892+
tmp = pvt->members;
893+
pvt->n_members = group->n_members;
894+
pvt->members = members;
895+
896+
for (uint32_t i = 0; i < count; i++)
897+
nexthop_decref(members[i].nh);
898+
899+
rte_rcu_qsbr_synchronize(gr_datapath_rcu(), RTE_QSBR_THRID_INVALID);
900+
rte_free(tmp);
901+
902+
group_list_add(nh);
903+
return 0;
904+
}
905+
906+
static struct gr_nexthop *group_to_api(const struct nexthop *nh, size_t *len) {
907+
const struct nexthop_info_group *group_priv = nexthop_info_group(nh);
908+
struct gr_nexthop_info_group *group_pub;
909+
struct gr_nexthop *pub;
910+
*len = sizeof(*pub) + sizeof(*group_pub)
911+
+ group_priv->n_members * sizeof(group_priv->members[0]);
912+
913+
pub = malloc(*len);
914+
if (pub == NULL) {
915+
len = 0;
916+
return errno_set_null(ENOMEM);
917+
}
918+
919+
pub->base = nh->base;
920+
group_pub = (struct gr_nexthop_info_group *)pub->info;
921+
922+
group_pub->n_members = group_priv->n_members;
923+
for (uint32_t i = 0; i < group_pub->n_members; i++) {
924+
group_pub->members[i].nh_id = group_priv->members[i].nh->nh_id;
925+
group_pub->members[i].weight = group_priv->members[i].weight;
926+
}
927+
928+
return pub;
929+
}
930+
931+
static struct nexthop_type_ops group_nh_ops = {
932+
.equal = group_equal,
933+
.free = group_free,
934+
.import_info = group_import_info,
935+
.to_api = group_to_api,
936+
};
937+
798938
RTE_INIT(init) {
799939
gr_event_register_serializer(&nh_serializer);
800940
gr_register_module(&module);
801941
rte_telemetry_register_cmd(
802942
"/grout/nexthop/stats", telemetry_nexthop_stats_get, "Get nexthop statistics"
803943
);
804944
nexthop_type_ops_register(GR_NH_T_L3, &l3_nh_ops);
945+
nexthop_type_ops_register(GR_NH_T_GROUP, &group_nh_ops);
805946
}

smoke/config_test.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ grcli nexthop add l3 iface p0 address ba4:f00::1 mac ba:d0:ca:ca:00:02
1616
grcli nexthop add l3 iface p1 address 4.3.2.1 mac ba:d0:ca:ca:00:01
1717
grcli nexthop add blackhole id 666
1818
grcli nexthop add reject id 123456
19+
grcli nexthop add group id 333 members nh 42 weight 102
20+
grcli nexthop add group id 333 members nh 45 nh 47
1921
grcli address add 10.0.0.1/24 iface p0
2022
grcli address add 10.1.0.1/24 iface p1
2123
grcli route add 0.0.0.0/0 via 10.0.0.2
@@ -40,6 +42,7 @@ grcli stats show hardware
4042
grcli nexthop del 42
4143
grcli nexthop del 666
4244
grcli nexthop del 123456
45+
grcli nexthop del 333
4346

4447
grcli interface del p0
4548
grcli interface del p1

0 commit comments

Comments
 (0)