platform: manage ECMP routes

When reading from netlink an ECMP IPv4 route, we need to parse the
multiple nexthops. In order to do that, we are introducing
NMPlatformIP4RtNextHop struct.

The first nexthop information will be kept at the original
NMPlatformIP4Route and the new property n_nexthops will indicate how
many nexthops we need to consider.
This commit is contained in:
Fernando Fernandez Mancera
2022-09-15 14:03:51 +02:00
parent e2b343c41c
commit 1bbdecf5e1
6 changed files with 563 additions and 142 deletions

View File

@@ -341,6 +341,7 @@ test_ip4_route(void)
rts[0].metric = metric;
rts[0].mss = mss;
rts[0].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_LINK);
rts[0].n_nexthops = 1;
rts[1].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
rts[1].network = network;
rts[1].plen = plen;
@@ -349,6 +350,7 @@ test_ip4_route(void)
rts[1].metric = metric;
rts[1].mss = mss;
rts[1].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE);
rts[1].n_nexthops = 1;
rts[2].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
rts[2].network = 0;
rts[2].plen = 0;
@@ -357,6 +359,7 @@ test_ip4_route(void)
rts[2].metric = metric;
rts[2].mss = mss;
rts[2].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE);
rts[2].n_nexthops = 1;
g_assert_cmpint(routes->len, ==, 3);
nmtst_platform_ip4_routes_equal_aptr((const NMPObject *const *) routes->pdata,
rts,
@@ -650,6 +653,7 @@ test_ip4_route_options(gconstpointer test_data)
.mss = 1300,
.quickack = TRUE,
.rto_min = 1000,
.n_nexthops = 1,
});
break;
case 2:
@@ -669,6 +673,7 @@ test_ip4_route_options(gconstpointer test_data)
.gateway = nmtst_inet4_from_string("172.16.1.1"),
.plen = 24,
.metric = 20,
.n_nexthops = 1,
});
rts_add[rts_n++] = ((NMPlatformIP4Route){
.ifindex = IFINDEX,
@@ -678,6 +683,7 @@ test_ip4_route_options(gconstpointer test_data)
.r_rtm_flags = RTNH_F_ONLINK,
.plen = 24,
.metric = 20,
.n_nexthops = 1,
});
break;
default:

View File

@@ -3640,12 +3640,18 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter
struct {
gboolean found;
gboolean has_more;
guint8 weight;
int ifindex;
NMIPAddr gateway;
} nh = {
.found = FALSE,
.has_more = FALSE,
};
guint v4_n_nexthops = 0;
NMPlatformIP4RtNextHop v4_nh_extra_nexthops_stack[10];
gs_free NMPlatformIP4RtNextHop *v4_nh_extra_nexthops_heap = NULL;
NMPlatformIP4RtNextHop *v4_nh_extra_nexthops = v4_nh_extra_nexthops_stack;
guint v4_nh_extra_alloc = G_N_ELEMENTS(v4_nh_extra_nexthops_stack);
guint32 mss;
guint32 window = 0;
guint32 cwnd = 0;
@@ -3712,9 +3718,57 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter
idx = 0;
while (TRUE) {
if (idx == multihop_idx) {
if (nh.found && IS_IPv4) {
NMPlatformIP4RtNextHop *new_nexthop;
/* we parsed the first IPv4 nexthop in "nh", let's parse the following ones.
*
* At this point, v4_n_nexthops still counts how many hops we already added,
* now we are about to add the (v4_n_nexthops+1) hop.
*
* Note that the first hop (of then v4_n_nexthops) is tracked in "nh".
* v4_nh_extra_nexthops tracks the additional hops.
*
* v4_nh_extra_alloc is how many space is allocated for
* v4_nh_extra_nexthops (note that in the end we will only add (v4_n_nexthops-1)
* hops in this list). */
nm_assert(v4_n_nexthops > 0u);
if (v4_n_nexthops - 1u >= v4_nh_extra_alloc) {
v4_nh_extra_alloc = NM_MAX(4, v4_nh_extra_alloc * 2u);
if (!v4_nh_extra_nexthops_heap) {
v4_nh_extra_nexthops_heap =
g_new(NMPlatformIP4RtNextHop, v4_nh_extra_alloc);
memcpy(v4_nh_extra_nexthops_heap,
v4_nh_extra_nexthops_stack,
G_N_ELEMENTS(v4_nh_extra_nexthops_stack));
} else {
v4_nh_extra_nexthops_heap = g_renew(NMPlatformIP4RtNextHop,
v4_nh_extra_nexthops_heap,
v4_nh_extra_alloc);
}
v4_nh_extra_nexthops = v4_nh_extra_nexthops_heap;
}
nm_assert(v4_n_nexthops - 1u < v4_nh_extra_alloc);
new_nexthop = &v4_nh_extra_nexthops[v4_n_nexthops - 1u];
new_nexthop->ifindex = rtnh->rtnh_ifindex;
new_nexthop->weight = NM_MAX(rtnh->rtnh_hops, 1u);
if (rtnh->rtnh_len > sizeof(*rtnh)) {
struct nlattr *ntb[RTA_MAX + 1];
if (nla_parse_arr(ntb,
(struct nlattr *) RTNH_DATA(rtnh),
rtnh->rtnh_len - sizeof(*rtnh),
NULL)
< 0)
return NULL;
if (_check_addr_or_return_null(ntb, RTA_GATEWAY, addr_len))
memcpy(&new_nexthop->gateway, nla_data(ntb[RTA_GATEWAY]), addr_len);
}
} else if (IS_IPv4 || idx == multihop_idx) {
nh.found = TRUE;
nh.ifindex = rtnh->rtnh_ifindex;
nh.weight = NM_MAX(rtnh->rtnh_hops, 1u);
if (rtnh->rtnh_len > sizeof(*rtnh)) {
struct nlattr *ntb[RTA_MAX + 1];
@@ -3731,15 +3785,6 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter
} else if (nh.found) {
/* we just parsed a nexthop, but there is yet another hop afterwards. */
nm_assert(idx == multihop_idx + 1);
if (IS_IPv4) {
/* for IPv4, multihop routes are currently not supported.
*
* If we ever support them, then the next-hop list is part of the NMPlatformIPRoute,
* that is, for IPv4 we truly have multihop routes. Unlike for IPv6.
*
* For now, just error out. */
return NULL;
}
/* For IPv6 multihop routes, we need to remember to iterate again.
* For each next-hop, we will create a distinct single-hop NMPlatformIP6Route. */
@@ -3747,6 +3792,9 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter
break;
}
if (IS_IPv4)
v4_n_nexthops++;
if (tlen < RTNH_ALIGN(rtnh->rtnh_len) + sizeof(*rtnh))
break;
@@ -3780,6 +3828,9 @@ rta_multipath_done:
nh.ifindex = ifindex;
nh.gateway = gateway;
nh.found = TRUE;
nm_assert(v4_n_nexthops == 0);
if (IS_IPv4)
v4_n_nexthops = 1;
} else {
/* Kernel supports new style nexthop configuration,
* verify that it is a duplicate and ignore old-style nexthop. */
@@ -3870,6 +3921,23 @@ rta_multipath_done:
obj->ip_route.ifindex = nh.ifindex;
if (IS_IPv4) {
nm_assert((!!nh.found) == (v4_n_nexthops > 0u));
obj->ip4_route.n_nexthops = v4_n_nexthops;
if (v4_n_nexthops > 1) {
/* We only set the weight for multihop routes. I think that corresponds to what kernel
* does. The weight is mostly undefined for single-hop. */
obj->ip4_route.weight = NM_MAX(nh.weight, 1u);
obj->_ip4_route.extra_nexthops =
(v4_nh_extra_alloc == v4_n_nexthops - 1u
&& v4_nh_extra_nexthops == v4_nh_extra_nexthops_heap)
? g_steal_pointer(&v4_nh_extra_nexthops_heap)
: nm_memdup(v4_nh_extra_nexthops,
sizeof(v4_nh_extra_nexthops[0]) * (v4_n_nexthops - 1u));
}
}
if (_check_addr_or_return_null(tb, RTA_DST, addr_len))
memcpy(obj->ip_route.network_ptr, nla_data(tb[RTA_DST]), addr_len);
@@ -5180,6 +5248,39 @@ _nl_msg_new_route(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPObject *ob
NLA_PUT(msg, RTA_PREFSRC, addr_len, &obj->ip6_route.pref_src);
}
if (IS_IPv4 && obj->ip4_route.n_nexthops > 1u) {
struct nlattr *multipath;
guint i;
if (!(multipath = nla_nest_start(msg, RTA_MULTIPATH)))
goto nla_put_failure;
for (i = 0u; i < obj->ip4_route.n_nexthops; i++) {
struct rtnexthop *rtnh;
rtnh = nlmsg_reserve(msg, sizeof(*rtnh), NLMSG_ALIGNTO);
if (!rtnh)
goto nla_put_failure;
if (i == 0u) {
rtnh->rtnh_hops = NM_MAX(obj->ip4_route.weight, 1u);
rtnh->rtnh_ifindex = obj->ip4_route.ifindex;
NLA_PUT_U32(msg, RTA_GATEWAY, obj->ip4_route.gateway);
} else {
const NMPlatformIP4RtNextHop *n = &obj->_ip4_route.extra_nexthops[i - 1u];
rtnh->rtnh_hops = NM_MAX(n->weight, 1u);
rtnh->rtnh_ifindex = n->ifindex;
NLA_PUT_U32(msg, RTA_GATEWAY, n->gateway);
}
rtnh->rtnh_flags = 0;
rtnh->rtnh_len = (char *) nlmsg_tail(nlmsg_hdr(msg)) - (char *) rtnh;
}
nla_nest_end(msg, multipath);
}
if (obj->ip_route.mss || obj->ip_route.window || obj->ip_route.cwnd || obj->ip_route.initcwnd
|| obj->ip_route.initrwnd || obj->ip_route.mtu || obj->ip_route.quickack
|| obj->ip_route.rto_min || lock) {

View File

@@ -5206,7 +5206,30 @@ nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *r
int
nm_platform_ip4_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route)
{
return _ip_route_add(self, flags, AF_INET, route);
gs_free NMPlatformIP4RtNextHop *extra_nexthops_free = NULL;
NMPObject obj;
nm_assert(route);
nm_assert(route->n_nexthops <= 1u || extra_nexthops);
nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP4_ROUTE, (const NMPlatformObject *) route);
if (route->n_nexthops > 1u) {
nm_assert(extra_nexthops);
/* we need to ensure that @extra_nexthops stays alive until the function returns.
* Copy the buffer.
*
* This is probably not necessary, because likely the caller will somehow ensure that
* the extra_nexthops stay alive. Still do it, because it is a very unusual case and
* likely cheap. */
obj._ip4_route.extra_nexthops =
nm_memdup_maybe_a(500u,
extra_nexthops,
sizeof(extra_nexthops[0]) * (route->n_nexthops - 1u),
&extra_nexthops_free);
}
return _ip_route_add(self, flags, &obj);
}
int
@@ -6606,6 +6629,10 @@ _rtm_flags_to_string_full(char *buf, gsize buf_size, unsigned rtm_flags)
/**
* nm_platform_ip4_route_to_string:
* @route: pointer to NMPlatformIP4Route route structure
* @extra_nexthops: (allow-none): the route might be a ECMP multihop route
* (with n_nexthops > 1). In that case, provide the list of extra hops
* to print too. It is allowed for a multihop route to omit the extra hops
* by passing NULL.
* @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
* @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
*
@@ -6616,8 +6643,12 @@ _rtm_flags_to_string_full(char *buf, gsize buf_size, unsigned rtm_flags)
* Returns: a string representation of the route.
*/
const char *
nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsize len)
nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route,
const NMPlatformIP4RtNextHop *extra_nexthops,
char *buf,
gsize len)
{
char *buf0;
char s_network[INET_ADDRSTRLEN];
char s_gateway[INET_ADDRSTRLEN];
char s_pref_src[INET_ADDRSTRLEN];
@@ -6636,10 +6667,16 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz
char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN];
char str_type[30];
char str_metric[30];
char weight_str[20];
guint n_nexthops;
if (!nm_utils_to_string_buffer_init_null(route, &buf, &len))
return buf;
buf0 = buf;
n_nexthops = nm_platform_ip4_route_get_n_nexthops(route);
inet_ntop(AF_INET, &route->network, s_network, sizeof(s_network));
if (route->gateway == 0)
@@ -6647,14 +6684,15 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz
else
inet_ntop(AF_INET, &route->gateway, s_gateway, sizeof(s_gateway));
g_snprintf(
buf,
len,
nm_strbuf_append(
&buf,
&len,
"type %s " /* type */
"%s" /* table */
"%s/%d"
"%s%s" /* gateway */
"%s"
"%s%s" /* weight */
"%s" /* dev/ifindex */
" metric %s"
"%s" /* mss */
" rt-src %s" /* protocol */
@@ -6682,9 +6720,13 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz
: ""),
s_network,
route->plen,
s_gateway[0] ? " via " : "",
s_gateway,
_to_string_dev(str_dev, route->ifindex),
n_nexthops <= 1 && s_gateway[0] ? " via " : "",
n_nexthops <= 1 ? s_gateway : "",
NM_PRINT_FMT_QUOTED2(n_nexthops <= 1 && route->weight != 0,
" weight ",
nm_sprintf_buf(weight_str, "%u", route->weight),
""),
n_nexthops <= 1 ? _to_string_dev(str_dev, route->ifindex) : "",
route->metric_any
? (route->metric ? nm_sprintf_buf(str_metric, "??+%u", route->metric) : "??")
: nm_sprintf_buf(str_metric, "%u", route->metric),
@@ -6734,7 +6776,56 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz
route->mtu)
: "",
route->r_force_commit ? " force-commit" : "");
return buf;
if ((n_nexthops == 1 && route->ifindex > 0) || n_nexthops == 0) {
/* A plain single hop route. Nothing extra to remark. */
} else {
nm_strbuf_append(&buf, &len, " n_nexthops %u", n_nexthops);
if (n_nexthops > 1) {
nm_strbuf_append(&buf,
&len,
" nexthop"
"%s%s" /* gateway */
"%s%s" /* weight */
"%s" /* dev/ifindex */
"",
s_gateway[0] ? " via " : "",
s_gateway,
NM_PRINT_FMT_QUOTED2(route->weight != 1,
" weight ",
nm_sprintf_buf(weight_str, "%u", route->weight),
""),
_to_string_dev(str_dev, route->ifindex));
if (!extra_nexthops)
nm_strbuf_append_str(&buf, &len, " nexthops [...]");
else {
guint i;
for (i = 1; i < n_nexthops; i++) {
const NMPlatformIP4RtNextHop *nexthop = &extra_nexthops[i - 1];
nm_strbuf_append(
&buf,
&len,
" nexthop"
"%s" /* ifindex */
"%s%s" /* gateway */
"%s%s" /* weight */
"",
NM_PRINT_FMT_QUOTED2(nexthop->gateway != 0 || nexthop->ifindex <= 0,
" via ",
nm_inet4_ntop(nexthop->gateway, s_gateway),
""),
_to_string_dev(str_dev, nexthop->ifindex),
NM_PRINT_FMT_QUOTED2(nexthop->weight != 1,
" weight ",
nm_sprintf_buf(weight_str, "%u", nexthop->weight),
""));
}
}
}
}
return buf0;
}
/**
@@ -8072,6 +8163,20 @@ nm_platform_lnk_wireguard_cmp(const NMPlatformLnkWireGuard *a, const NMPlatformL
return 0;
}
void
nm_platform_ip4_rt_nexthop_hash_update(const NMPlatformIP4RtNextHop *obj,
gboolean for_id,
NMHashState *h)
{
guint8 w;
nm_assert(obj);
w = for_id ? NM_MAX(obj->weight, 1u) : obj->weight;
nm_hash_update_vals(h, obj->ifindex, obj->gateway, w);
}
void
nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
NMPlatformIPRouteCmpType cmp_type,
@@ -8101,7 +8206,9 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
obj->ifindex,
nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source),
_ip_route_scope_inv_get_normalized(obj),
nm_platform_ip4_route_get_n_nexthops(obj),
obj->gateway,
(guint8) NM_MAX(obj->weight, 1u),
obj->mss,
obj->pref_src,
obj->window,
@@ -8131,7 +8238,9 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
nm_ip4_addr_clear_host_address(obj->network, obj->plen),
obj->plen,
obj->metric,
nm_platform_ip4_route_get_n_nexthops(obj),
obj->gateway,
(guint8) NM_MAX(obj->weight, 1u),
nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source),
_ip_route_scope_inv_get_normalized(obj),
obj->tos,
@@ -8164,6 +8273,8 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
obj->plen,
obj->metric,
obj->gateway,
obj->n_nexthops,
obj->weight,
obj->rt_source,
obj->scope_inv,
obj->tos,
@@ -8191,6 +8302,30 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
}
}
int
nm_platform_ip4_rt_nexthop_cmp(const NMPlatformIP4RtNextHop *a,
const NMPlatformIP4RtNextHop *b,
gboolean for_id)
{
guint8 w_a;
guint8 w_b;
/* Note that weight zero is not valid (in kernel). We thus treat
* weight zero usually the same as 1.
*
* Not here for cmp/hash_update functions. These functions check for the exact
* bit-pattern, and not the it means at other places. */
NM_CMP_SELF(a, b);
NM_CMP_FIELD(a, b, ifindex);
NM_CMP_FIELD(a, b, gateway);
w_a = for_id ? NM_MAX(a->weight, 1u) : a->weight;
w_b = for_id ? NM_MAX(b->weight, 1u) : b->weight;
NM_CMP_DIRECT(w_a, w_b);
return 0;
}
int
nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
const NMPlatformIP4Route *b,
@@ -8215,7 +8350,10 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
nmp_utils_ip_config_source_round_trip_rtprot(b->rt_source));
NM_CMP_DIRECT(_ip_route_scope_inv_get_normalized(a),
_ip_route_scope_inv_get_normalized(b));
NM_CMP_DIRECT(nm_platform_ip4_route_get_n_nexthops(a),
nm_platform_ip4_route_get_n_nexthops(b));
NM_CMP_FIELD(a, b, gateway);
NM_CMP_DIRECT(NM_MAX(a->weight, 1u), NM_MAX(b->weight, 1u));
NM_CMP_FIELD(a, b, mss);
NM_CMP_FIELD(a, b, pref_src);
NM_CMP_FIELD(a, b, window);
@@ -8251,7 +8389,16 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
NM_CMP_FIELD(a, b, plen);
NM_CMP_FIELD_UNSAFE(a, b, metric_any);
NM_CMP_FIELD(a, b, metric);
if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
NM_CMP_DIRECT(nm_platform_ip4_route_get_n_nexthops(a),
nm_platform_ip4_route_get_n_nexthops(b));
} else
NM_CMP_FIELD(a, b, n_nexthops);
NM_CMP_FIELD(a, b, gateway);
if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
NM_CMP_DIRECT(NM_MAX(a->weight, 1u), NM_MAX(b->weight, 1u));
else
NM_CMP_FIELD(a, b, weight);
if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->rt_source),
nmp_utils_ip_config_source_round_trip_rtprot(b->rt_source));
@@ -8913,7 +9060,10 @@ log_ip4_route(NMPlatform *self,
_LOG3D("signal: route 4 %7s: %s",
nm_platform_signal_change_type_to_string(change_type),
nm_platform_ip4_route_to_string(route, sbuf, sizeof(sbuf)));
nmp_object_to_string(NMP_OBJECT_UP_CAST(route),
NMP_OBJECT_TO_STRING_PUBLIC,
sbuf,
sizeof(sbuf)));
}
static void
@@ -8928,7 +9078,10 @@ log_ip6_route(NMPlatform *self,
_LOG3D("signal: route 6 %7s: %s",
nm_platform_signal_change_type_to_string(change_type),
nm_platform_ip6_route_to_string(route, sbuf, sizeof(sbuf)));
nmp_object_to_string(NMP_OBJECT_UP_CAST(route),
NMP_OBJECT_TO_STRING_PUBLIC,
sbuf,
sizeof(sbuf)));
}
static void

View File

@@ -382,9 +382,27 @@ typedef struct {
struct _NMPlatformIP4Route {
__NMPlatformIPRoute_COMMON;
in_addr_t network;
/* RTA_GATEWAY. The gateway is part of the primary key for a route */
/* If n_nexthops is zero, the the address has no next hops. That applies
* to certain route types like blackhole.
* If n_nexthops is 1, then the fields "ifindex", "gateway" and "weight"
* are the first next-hop. There are no further nexthops.
* If n_nexthops is greater than 1, the first next hop is in the fields
* "ifindex", "gateway", "weight", and the (n_nexthops-1) hops are in
* NMPObjectIP4Route.extra_nexthops field (outside the NMPlatformIP4Route
* struct).
*
* For convenience, if ifindex > 0 and n_nexthops == 0, we assume that n_nexthops
* is in fact 1. If ifindex is <= 0, n_nexthops must be zero.
* See nm_platform_ip4_route_get_n_nexthops(). */
guint n_nexthops;
/* RTA_GATEWAY. The gateway is part of the primary key for a route.
* If n_nexthops is zero, this value is undefined (should be zero).
* If n_nexthops is greater or equal to one, this is the gateway of
* the first hop. */
in_addr_t gateway;
/* RTA_PREFSRC (called "src" by iproute2).
@@ -412,6 +430,21 @@ struct _NMPlatformIP4Route {
* For IPv6 routes, the scope is ignored and kernel always assumes global scope.
* Hence, this field is only in NMPlatformIP4Route. */
guint8 scope_inv;
/* This is the weight of for the first next-hop, in case of n_nexthops > 1.
*
* If n_nexthops is zero, this value is undefined (should be zero).
* If n_nexthops is 1, this also doesn't matter, but it's usually set to
* zero.
* If n_nexthops is greater or equal to one, this is the weight of
* the first hop.
*
* Note that upper layers use this flag to indicate whether this is a multihop route.
* Single-hop, non-ECMP routes will have a weight of zero.
*
* The valid range for weight is 1-255. For convenience, we treat 0 the same
* as 1 for multihop routes. */
guint8 weight;
};
struct _NMPlatformIP6Route {
@@ -636,6 +669,14 @@ typedef struct {
bool proto_ad : 1;
} NMPlatformVFVlan;
typedef struct {
int ifindex;
in_addr_t gateway;
/* The valid range for weight is 1-255. For convenience, we treat 0 the same
* as 1 for multihop routes. */
guint8 weight;
} NMPlatformIP4RtNextHop;
typedef struct {
guint num_vlans;
guint32 index;
@@ -2129,6 +2170,23 @@ nm_platform_ip4_route_get_effective_metric(const NMPlatformIP4Route *r)
: r->metric;
}
static inline guint
nm_platform_ip4_route_get_n_nexthops(const NMPlatformIP4Route *r)
{
/* The first hop of the "n_nexthops" is in NMPlatformIP4Route
* itself. Thus, if the caller only sets ifindex and leaves
* n_nexthops at zero, the number of next hops is still 1
* (for convenience of the user who wants to initialize a
* single hop route). */
if (r->n_nexthops >= 1) {
nm_assert(r->ifindex > 0);
return r->n_nexthops;
}
if (r->ifindex > 0)
return 1;
return 0;
}
static inline guint32
nm_platform_ip6_route_get_effective_metric(const NMPlatformIP6Route *r)
{
@@ -2216,7 +2274,18 @@ const char *nm_platform_lnk_vrf_to_string(const NMPlatformLnkVrf *lnk, char *buf
const char *nm_platform_lnk_vxlan_to_string(const NMPlatformLnkVxlan *lnk, char *buf, gsize len);
const char *
nm_platform_lnk_wireguard_to_string(const NMPlatformLnkWireGuard *lnk, char *buf, gsize len);
const char *nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsize len);
const char *nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route,
const NMPlatformIP4RtNextHop *extra_nexthops,
char *buf,
gsize len);
static inline const char *
nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsize len)
{
return nm_platform_ip4_route_to_string_full(route, NULL, buf, len);
}
const char *nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsize len);
const char *
nm_platform_routing_rule_to_string(const NMPlatformRoutingRule *routing_rule, char *buf, gsize len);
@@ -2260,6 +2329,9 @@ GHashTable *nm_platform_ip4_address_addr_to_hash(NMPlatform *self, int ifindex);
int nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
const NMPlatformIP4Route *b,
NMPlatformIPRouteCmpType cmp_type);
int nm_platform_ip4_rt_nexthop_cmp(const NMPlatformIP4RtNextHop *a,
const NMPlatformIP4RtNextHop *b,
gboolean for_id);
int nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a,
const NMPlatformIP6Route *b,
NMPlatformIPRouteCmpType cmp_type);
@@ -2298,6 +2370,9 @@ void nm_platform_link_hash_update(const NMPlatformLink *obj, NMHashState *h);
void nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
NMPlatformIPRouteCmpType cmp_type,
NMHashState *h);
void nm_platform_ip4_rt_nexthop_hash_update(const NMPlatformIP4RtNextHop *obj,
gboolean for_id,
NMHashState *h);
void nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
NMPlatformIPRouteCmpType cmp_type,
NMHashState *h);

View File

@@ -734,6 +734,12 @@ _vt_cmd_obj_dispose_link(NMPObject *obj)
nmp_object_unref(obj->_link.netlink.lnk);
}
static void
_vt_cmd_obj_dispose_ip4_route(NMPObject *obj)
{
nm_clear_g_free((gpointer *) &obj->_ip4_route.extra_nexthops);
}
static void
_vt_cmd_obj_dispose_lnk_vlan(NMPObject *obj)
{
@@ -848,14 +854,12 @@ nmp_object_stackinit_id(NMPObject *obj, const NMPObject *src)
if (klass->cmd_plobj_id_copy)
klass->cmd_plobj_id_copy(&obj->object, &src->object);
else {
/* This object must not implement cmd_obj_copy().
* If it would, it would mean that we require a deep copy
* of the data. As @obj is stack-allocated, it cannot track
* ownership. The caller must not use nmp_object_stackinit_id()
* with an object of such a type. */
nm_assert(!klass->cmd_obj_copy);
/* plain memcpy of the public part suffices. */
/* plain memcpy.
*
* Note that for NMPObjectIP4Route this also copies extra_nexthops
* pointer, aliasing it without taking ownership. That is potentially
* dangerous, but when using a stack allocated instance, you must
* always take care of ownership. */
memcpy(&obj->object, &src->object, klass->sizeof_data);
}
return obj;
@@ -992,6 +996,41 @@ _vt_cmd_obj_to_string_link(const NMPObject *obj,
}
}
static const char *
_vt_cmd_obj_to_string_ip4_route(const NMPObject *obj,
NMPObjectToStringMode to_string_mode,
char *buf,
gsize buf_size)
{
const NMPClass *klass;
char buf2[NM_UTILS_TO_STRING_BUFFER_SIZE];
klass = NMP_OBJECT_GET_CLASS(obj);
switch (to_string_mode) {
case NMP_OBJECT_TO_STRING_PUBLIC:
case NMP_OBJECT_TO_STRING_ID:
nm_platform_ip4_route_to_string_full(&obj->ip4_route,
obj->_ip4_route.extra_nexthops,
buf,
buf_size);
return buf;
case NMP_OBJECT_TO_STRING_ALL:
g_snprintf(buf,
buf_size,
"[%s," NM_HASH_OBFUSCATE_PTR_FMT ",%u,%calive,%cvisible; %s]",
klass->obj_type_name,
NM_HASH_OBFUSCATE_PTR(obj),
obj->parent._ref_count,
nmp_object_is_alive(obj) ? '+' : '-',
nmp_object_is_visible(obj) ? '+' : '-',
nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof(buf2)));
return buf;
default:
g_return_val_if_reached("ERROR");
}
}
static const char *
_vt_cmd_obj_to_string_lnk_vlan(const NMPObject *obj,
NMPObjectToStringMode to_string_mode,
@@ -1197,6 +1236,21 @@ _vt_cmd_obj_hash_update_link(const NMPObject *obj, gboolean for_id, NMHashState
nmp_object_hash_update(obj->_link.netlink.lnk, h);
}
static void
_vt_cmd_obj_hash_update_ip4_route(const NMPObject *obj, gboolean for_id, NMHashState *h)
{
guint i;
nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ROUTE);
nm_platform_ip4_route_hash_update(&obj->ip4_route,
for_id ? NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID
: NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL,
h);
for (i = 1u; i < obj->ip4_route.n_nexthops; i++)
nm_platform_ip4_rt_nexthop_hash_update(&obj->_ip4_route.extra_nexthops[i - 1u], for_id, h);
}
static void
_vt_cmd_obj_hash_update_lnk_vlan(const NMPObject *obj, gboolean for_id, NMHashState *h)
{
@@ -1298,6 +1352,8 @@ nmp_object_cmp_full(const NMPObject *obj1, const NMPObject *obj2, NMPObjectCmpFl
} else if (obj1->obj_with_ifindex.ifindex != obj2->obj_with_ifindex.ifindex) {
nmp_object_stackinit(&obj_stackcopy, klass->obj_type, &obj2->obj_with_ifindex);
obj_stackcopy.obj_with_ifindex.ifindex = obj1->obj_with_ifindex.ifindex;
if (klass->obj_type == NMP_OBJECT_TYPE_IP4_ROUTE)
obj_stackcopy._ip4_route.extra_nexthops = obj2->_ip4_route.extra_nexthops;
obj2 = &obj_stackcopy;
}
}
@@ -1335,6 +1391,28 @@ _vt_cmd_obj_cmp_link(const NMPObject *obj1, const NMPObject *obj2, gboolean for_
return 0;
}
static int
_vt_cmd_obj_cmp_ip4_route(const NMPObject *obj1, const NMPObject *obj2, gboolean for_id)
{
int c;
guint i;
c = nm_platform_ip4_route_cmp(&obj1->ip4_route,
&obj2->ip4_route,
for_id ? NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID
: NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL);
NM_CMP_RETURN_DIRECT(c);
for (i = 1u; i < obj1->ip4_route.n_nexthops; i++) {
c = nm_platform_ip4_rt_nexthop_cmp(&obj1->_ip4_route.extra_nexthops[i - 1u],
&obj2->_ip4_route.extra_nexthops[i - 1u],
for_id);
NM_CMP_RETURN_DIRECT(c);
}
return 0;
}
static int
_vt_cmd_obj_cmp_lnk_vlan(const NMPObject *obj1, const NMPObject *obj2, gboolean for_id)
{
@@ -1438,6 +1516,28 @@ _vt_cmd_obj_copy_link(NMPObject *dst, const NMPObject *src)
dst->_link = src->_link;
}
static void
_vt_cmd_obj_copy_ip4_route(NMPObject *dst, const NMPObject *src)
{
nm_assert(dst != src);
if (src->ip4_route.n_nexthops <= 1) {
nm_clear_g_free((gpointer *) &dst->_ip4_route.extra_nexthops);
} else if (src->ip4_route.n_nexthops != dst->ip4_route.n_nexthops
|| !nm_memeq_n(src->_ip4_route.extra_nexthops,
src->ip4_route.n_nexthops - 1u,
dst->_ip4_route.extra_nexthops,
dst->ip4_route.n_nexthops - 1u,
sizeof(NMPlatformIP4RtNextHop))) {
nm_clear_g_free((gpointer *) &dst->_ip4_route.extra_nexthops);
dst->_ip4_route.extra_nexthops =
nm_memdup(src->_ip4_route.extra_nexthops,
sizeof(NMPlatformIP4RtNextHop) * (src->ip4_route.n_nexthops - 1u));
}
dst->ip4_route = src->ip4_route;
}
static void
_vt_cmd_obj_copy_lnk_vlan(NMPObject *dst, const NMPObject *src)
{
@@ -1577,14 +1677,6 @@ _vt_cmd_plobj_id_cmp(tfilter, NMPlatformTfilter, {
NM_CMP_FIELD(obj1, obj2, handle);
});
static int
_vt_cmd_plobj_id_cmp_ip4_route(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
{
return nm_platform_ip4_route_cmp((const NMPlatformIP4Route *) obj1,
(const NMPlatformIP4Route *) obj2,
NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID);
}
static int
_vt_cmd_plobj_id_cmp_ip6_route(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
{
@@ -1673,10 +1765,6 @@ _vt_cmd_plobj_id_hash_update(ip6_address, NMPlatformIP6Address, {
obj->address);
});
_vt_cmd_plobj_id_hash_update(ip4_route, NMPlatformIP4Route, {
nm_platform_ip4_route_hash_update(obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h);
});
_vt_cmd_plobj_id_hash_update(ip6_route, NMPlatformIP6Route, {
nm_platform_ip6_route_hash_update(obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h);
});
@@ -1699,14 +1787,6 @@ _vt_cmd_plobj_id_hash_update(mptcp_addr, NMPlatformMptcpAddr, {
nm_hash_update(h, &obj->addr, nm_utils_addr_family_to_size_untrusted(obj->addr_family));
});
static void
_vt_cmd_plobj_hash_update_ip4_route(const NMPlatformObject *obj, NMHashState *h)
{
return nm_platform_ip4_route_hash_update((const NMPlatformIP4Route *) obj,
NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL,
h);
}
static void
_vt_cmd_plobj_hash_update_ip6_route(const NMPlatformObject *obj, NMHashState *h)
{
@@ -3229,12 +3309,11 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
.signal_type = NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,
.supported_cache_ids = _supported_cache_ids_ipx_route,
.cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_route,
.cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip4_route,
.cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip4_route,
.cmd_plobj_to_string_id = (CmdPlobjToStringIdFunc) nm_platform_ip4_route_to_string,
.cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_ip4_route_to_string,
.cmd_plobj_hash_update = _vt_cmd_plobj_hash_update_ip4_route,
.cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_ip4_route_cmp_full,
.cmd_obj_hash_update = _vt_cmd_obj_hash_update_ip4_route,
.cmd_obj_cmp = _vt_cmd_obj_cmp_ip4_route,
.cmd_obj_copy = _vt_cmd_obj_copy_ip4_route,
.cmd_obj_dispose = _vt_cmd_obj_dispose_ip4_route,
.cmd_obj_to_string = _vt_cmd_obj_to_string_ip4_route,
},
[NMP_OBJECT_TYPE_IP6_ROUTE - 1] =
{

View File

@@ -314,6 +314,13 @@ typedef struct {
typedef struct {
NMPlatformIP4Route _public;
/* The first hop is embedded in _public (in the
* ifindex, gateway and weight fields).
* Only if _public.n_nexthops is greater than 1, then
* this contains the remaining(!!) (_public.n_nexthops - 1)
* extra hops for ECMP multihop routes. */
const NMPlatformIP4RtNextHop *extra_nexthops;
} NMPObjectIP4Route;
typedef struct {