summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFernando Fernandez Mancera <ffmancera@riseup.net>2022-09-15 14:03:51 +0200
committerFernando Fernandez Mancera <ffmancera@riseup.net>2022-11-21 11:18:03 +0100
commit1bbdecf5e125a2ae37823f464c75c93f42dcd98a (patch)
tree95bf7fcb6e25c2cba71c3bc0095741bef02c5b9f
parente2b343c41cb7b579de58c718f47d8d25c3033f01 (diff)
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.
-rw-r--r--src/core/platform/tests/test-route.c96
-rw-r--r--src/libnm-platform/nm-linux-platform.c139
-rw-r--r--src/libnm-platform/nm-platform.c215
-rw-r--r--src/libnm-platform/nm-platform.h79
-rw-r--r--src/libnm-platform/nmp-object.c171
-rw-r--r--src/libnm-platform/nmp-object.h7
6 files changed, 564 insertions, 143 deletions
diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c
index 871263c61f..c441f58410 100644
--- a/src/core/platform/tests/test-route.c
+++ b/src/core/platform/tests/test-route.c
@@ -333,30 +333,33 @@ test_ip4_route(void)
/* Test route listing */
routes = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, ifindex);
memset(rts, 0, sizeof(rts));
- rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
- rts[0].network = gateway;
- rts[0].plen = 32;
- rts[0].ifindex = ifindex;
- rts[0].gateway = INADDR_ANY;
- rts[0].metric = metric;
- rts[0].mss = mss;
- rts[0].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_LINK);
- 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;
- rts[1].ifindex = ifindex;
- rts[1].gateway = gateway;
- rts[1].metric = metric;
- rts[1].mss = mss;
- rts[1].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE);
- 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;
- rts[2].ifindex = ifindex;
- rts[2].gateway = gateway;
- rts[2].metric = metric;
- rts[2].mss = mss;
- rts[2].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE);
+ rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
+ rts[0].network = gateway;
+ rts[0].plen = 32;
+ rts[0].ifindex = ifindex;
+ rts[0].gateway = INADDR_ANY;
+ 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;
+ rts[1].ifindex = ifindex;
+ rts[1].gateway = gateway;
+ 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;
+ rts[2].ifindex = ifindex;
+ rts[2].gateway = gateway;
+ 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,
@@ -635,21 +638,22 @@ test_ip4_route_options(gconstpointer test_data)
switch (TEST_IDX) {
case 1:
rts_add[rts_n++] = ((NMPlatformIP4Route){
- .ifindex = IFINDEX,
- .rt_source = NM_IP_CONFIG_SOURCE_USER,
- .network = nmtst_inet4_from_string("172.16.1.0"),
- .plen = 24,
- .metric = 20,
- .tos = 0x28,
- .window = 10000,
- .cwnd = 16,
- .initcwnd = 30,
- .initrwnd = 50,
- .mtu = 1350,
- .lock_cwnd = TRUE,
- .mss = 1300,
- .quickack = TRUE,
- .rto_min = 1000,
+ .ifindex = IFINDEX,
+ .rt_source = NM_IP_CONFIG_SOURCE_USER,
+ .network = nmtst_inet4_from_string("172.16.1.0"),
+ .plen = 24,
+ .metric = 20,
+ .tos = 0x28,
+ .window = 10000,
+ .cwnd = 16,
+ .initcwnd = 30,
+ .initrwnd = 50,
+ .mtu = 1350,
+ .lock_cwnd = TRUE,
+ .mss = 1300,
+ .quickack = TRUE,
+ .rto_min = 1000,
+ .n_nexthops = 1,
});
break;
case 2:
@@ -663,12 +667,13 @@ test_ip4_route_options(gconstpointer test_data)
.n_ifa_flags = 0,
});
rts_add[rts_n++] = ((NMPlatformIP4Route){
- .ifindex = IFINDEX,
- .rt_source = NM_IP_CONFIG_SOURCE_USER,
- .network = nmtst_inet4_from_string("172.17.1.0"),
- .gateway = nmtst_inet4_from_string("172.16.1.1"),
- .plen = 24,
- .metric = 20,
+ .ifindex = IFINDEX,
+ .rt_source = NM_IP_CONFIG_SOURCE_USER,
+ .network = nmtst_inet4_from_string("172.17.1.0"),
+ .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:
diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c
index cc57ce4045..4ea67c1791 100644
--- a/src/libnm-platform/nm-linux-platform.c
+++ b/src/libnm-platform/nm-linux-platform.c
@@ -3640,21 +3640,27 @@ _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,
};
- guint32 mss;
- guint32 window = 0;
- guint32 cwnd = 0;
- guint32 initcwnd = 0;
- guint32 initrwnd = 0;
- guint32 mtu = 0;
- guint32 rto_min = 0;
- guint32 lock = 0;
- gboolean quickack = 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;
+ guint32 initcwnd = 0;
+ guint32 initrwnd = 0;
+ guint32 mtu = 0;
+ guint32 rto_min = 0;
+ guint32 lock = 0;
+ gboolean quickack = FALSE;
nm_assert((parse_nlmsg_iter->iter_more && parse_nlmsg_iter->ip6_route.next_multihop > 0)
|| (!parse_nlmsg_iter->iter_more && parse_nlmsg_iter->ip6_route.next_multihop == 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) {
diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c
index f8f715bc23..188db58ed0 100644
--- a/src/libnm-platform/nm-platform.c
+++ b/src/libnm-platform/nm-platform.c
@@ -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,30 +6643,40 @@ _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)
-{
- char s_network[INET_ADDRSTRLEN];
- char s_gateway[INET_ADDRSTRLEN];
- char s_pref_src[INET_ADDRSTRLEN];
- char str_dev[30];
- char str_mss[32];
- char str_table[30];
- char str_scope[30];
- char s_source[50];
- char str_tos[32];
- char str_window[32];
- char str_cwnd[32];
- char str_initcwnd[32];
- char str_initrwnd[32];
- char str_rto_min[32];
- char str_mtu[32];
- char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN];
- char str_type[30];
- char str_metric[30];
+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];
+ char str_dev[30];
+ char str_mss[32];
+ char str_table[30];
+ char str_scope[30];
+ char s_source[50];
+ char str_tos[32];
+ char str_window[32];
+ char str_cwnd[32];
+ char str_initcwnd[32];
+ char str_initrwnd[32];
+ char str_rto_min[32];
+ char str_mtu[32];
+ 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;
}
/**
@@ -8073,6 +8164,20 @@ nm_platform_lnk_wireguard_cmp(const NMPlatformLnkWireGuard *a, const NMPlatformL
}
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,
NMHashState *h)
@@ -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,
@@ -8192,6 +8303,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,
NMPlatformIPRouteCmpType cmp_type)
@@ -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
diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h
index 9b711254c9..6429e1d923 100644
--- a/src/libnm-platform/nm-platform.h
+++ b/src/libnm-platform/nm-platform.h
@@ -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 {
@@ -637,6 +670,14 @@ typedef struct {
} 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;
guint32 min_tx_rate;
@@ -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);
diff --git a/src/libnm-platform/nmp-object.c b/src/libnm-platform/nmp-object.c
index a6ad5f65b0..06457e3927 100644
--- a/src/libnm-platform/nmp-object.c
+++ b/src/libnm-platform/nmp-object.c
@@ -735,6 +735,12 @@ _vt_cmd_obj_dispose_link(NMPObject *obj)
}
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)
{
g_free((gpointer) obj->_lnk_vlan.ingress_qos_map);
@@ -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;
@@ -993,6 +997,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,
char *buf,
@@ -1198,6 +1237,21 @@ _vt_cmd_obj_hash_update_link(const NMPObject *obj, gboolean for_id, NMHashState
}
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)
{
nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LNK_VLAN);
@@ -1298,7 +1352,9 @@ 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;
- obj2 = &obj_stackcopy;
+ if (klass->obj_type == NMP_OBJECT_TYPE_IP4_ROUTE)
+ obj_stackcopy._ip4_route.extra_nexthops = obj2->_ip4_route.extra_nexthops;
+ obj2 = &obj_stackcopy;
}
}
@@ -1336,6 +1392,28 @@ _vt_cmd_obj_cmp_link(const NMPObject *obj1, const NMPObject *obj2, gboolean for_
}
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)
{
int c;
@@ -1439,6 +1517,28 @@ _vt_cmd_obj_copy_link(NMPObject *dst, const NMPObject *src)
}
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)
{
dst->lnk_vlan = src->lnk_vlan;
@@ -1578,14 +1678,6 @@ _vt_cmd_plobj_id_cmp(tfilter, NMPlatformTfilter, {
});
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)
{
return nm_platform_ip6_route_cmp((const NMPlatformIP6Route *) obj1,
@@ -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);
});
@@ -1700,14 +1788,6 @@ _vt_cmd_plobj_id_hash_update(mptcp_addr, NMPlatformMptcpAddr, {
});
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)
{
return nm_platform_ip6_route_hash_update((const NMPlatformIP6Route *) obj,
@@ -3218,23 +3298,22 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
},
[NMP_OBJECT_TYPE_IP4_ROUTE - 1] =
{
- .parent = DEDUP_MULTI_OBJ_CLASS_INIT(),
- .obj_type = NMP_OBJECT_TYPE_IP4_ROUTE,
- .sizeof_data = sizeof(NMPObjectIP4Route),
- .sizeof_public = sizeof(NMPlatformIP4Route),
- .obj_type_name = "ip4-route",
- .addr_family = AF_INET,
- .rtm_gettype = RTM_GETROUTE,
- .signal_type_id = NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,
- .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,
+ .parent = DEDUP_MULTI_OBJ_CLASS_INIT(),
+ .obj_type = NMP_OBJECT_TYPE_IP4_ROUTE,
+ .sizeof_data = sizeof(NMPObjectIP4Route),
+ .sizeof_public = sizeof(NMPlatformIP4Route),
+ .obj_type_name = "ip4-route",
+ .addr_family = AF_INET,
+ .rtm_gettype = RTM_GETROUTE,
+ .signal_type_id = NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,
+ .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_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] =
{
diff --git a/src/libnm-platform/nmp-object.h b/src/libnm-platform/nmp-object.h
index 090436d217..9fa260b57f 100644
--- a/src/libnm-platform/nmp-object.h
+++ b/src/libnm-platform/nmp-object.h
@@ -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 {