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:
@@ -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:
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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] =
|
||||
{
|
||||
|
@@ -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 {
|
||||
|
Reference in New Issue
Block a user