Beniamino Galvani
2025-06-26 11:55:45 +02:00
4 changed files with 154 additions and 11 deletions

View File

@@ -623,6 +623,79 @@ test_ip4_zero_gateway(void)
nmtstp_wait_for_signal(NM_PLATFORM_GET, 50);
}
static void
test_via(void)
{
int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME);
GPtrArray *routes;
NMPlatformIP4Route rts[1];
struct in6_addr gateway6;
const int metric = 22987;
NMPlatformIP4Route route4;
guint mss = 1000;
in_addr_t net4;
/* Test IPv4 routes with a IPv6 gateway (using RTA_VIA attribute) */
inet_pton(AF_INET6, "fd01::1", &gateway6);
inet_pton(AF_INET, "1.2.3.4", &net4);
/* Add direct route to IPv6 gateway: ip route add dev $DEV fd01::1/128 */
nmtstp_ip6_route_add(NM_PLATFORM_GET,
ifindex,
NM_IP_CONFIG_SOURCE_USER,
gateway6,
128,
in6addr_any,
in6addr_any,
metric,
mss);
g_assert(nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &gateway6, 128, metric, NULL, 0));
/* Add IPv4 route via IPv6 gateway: ip route add dev $DEV 1.2.3.4/32 via inet6 fd01::1 */
route4 = (NMPlatformIP4Route) {
.ifindex = ifindex,
.rt_source = NM_IP_CONFIG_SOURCE_USER,
.network = net4,
.plen = 32,
.metric = metric,
.via.addr_family = AF_INET6,
.via.addr.addr6 = gateway6,
.mss = mss,
};
g_assert(NMTST_NM_ERR_SUCCESS(
nm_platform_ip4_route_add(NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &route4, NULL)));
g_assert(nmtstp_ip4_route_get(NM_PLATFORM_GET, ifindex, net4, 32, metric, 0));
/* Test route listing */
routes = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, ifindex);
g_assert_cmpint(routes->len, ==, 1);
memset(rts, 0, sizeof(rts));
rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
rts[0].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_LINK);
rts[0].network = net4;
rts[0].plen = 32;
rts[0].ifindex = ifindex;
rts[0].gateway = INADDR_ANY;
rts[0].metric = metric;
rts[0].mss = mss;
rts[0].via.addr_family = AF_INET6;
rts[0].via.addr.addr6 = gateway6;
rts[0].n_nexthops = 1;
nmtst_platform_ip4_routes_equal_aptr((const NMPObject *const *) routes->pdata,
rts,
routes->len,
TRUE);
g_ptr_array_unref(routes);
/* Delete routes */
g_assert(nmtstp_platform_ip6_route_delete(NM_PLATFORM_GET, ifindex, gateway6, 128, metric));
g_assert(!nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &gateway6, 128, metric, NULL, 0));
g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, net4, 32, metric));
g_assert(!nmtstp_ip4_route_get(NM_PLATFORM_GET, ifindex, net4, 32, metric, 0));
}
static void
test_ip4_route_options(gconstpointer test_data)
{
@@ -2421,6 +2494,7 @@ _nmtstp_setup_tests(void)
add_test_func("/route/ip4_route_get", test_ip4_route_get);
add_test_func("/route/ip6_route_get", test_ip6_route_get);
add_test_func("/route/ip4_zero_gateway", test_ip4_zero_gateway);
add_test_func("/route/via", test_via);
}
if (nmtstp_is_root_test()) {

View File

@@ -4013,6 +4013,7 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter
[RTA_PREF] = {.type = NLA_U8},
[RTA_FLOW] = {.type = NLA_U32},
[RTA_CACHEINFO] = {.minlen = nm_offsetofend(struct rta_cacheinfo, rta_tsage)},
[RTA_VIA] = {.minlen = nm_offsetofend(struct rtvia, rtvia_family)},
[RTA_METRICS] = {.type = NLA_NESTED},
[RTA_MULTIPATH] = {.type = NLA_NESTED},
};
@@ -4029,9 +4030,11 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter
guint8 weight;
int ifindex;
NMIPAddr gateway;
gboolean is_via;
} nh = {
.found = FALSE,
.has_more = FALSE,
.is_via = FALSE,
};
guint v4_n_nexthops = 0;
NMPlatformIP4RtNextHop v4_nh_extra_nexthops_stack[10];
@@ -4200,13 +4203,26 @@ rta_multipath_done:
return nm_assert_unreachable_val(NULL);
}
if (tb[RTA_OIF] || tb[RTA_GATEWAY] || tb[RTA_FLOW]) {
if (tb[RTA_OIF] || tb[RTA_GATEWAY] || tb[RTA_FLOW] || tb[RTA_VIA]) {
int ifindex = 0;
NMIPAddr gateway = {};
if (tb[RTA_OIF])
ifindex = nla_get_u32(tb[RTA_OIF]);
if (_check_addr_or_return_null(tb, RTA_GATEWAY, addr_len))
if (tb[RTA_VIA]) {
struct rtvia *rtvia;
/* Used when the gateway family doesn't match the route family */
rtvia = nla_data(tb[RTA_VIA]);
if (rtvia->rtvia_family != AF_INET6)
return NULL;
if (nla_len(tb[RTA_VIA]) < sizeof(struct rtvia) + sizeof(struct in6_addr))
return NULL;
nh.is_via = TRUE;
memcpy(&gateway, rtvia->rtvia_addr, sizeof(struct in6_addr));
} else if (_check_addr_or_return_null(tb, RTA_GATEWAY, addr_len))
memcpy(&gateway, nla_data(tb[RTA_GATEWAY]), addr_len);
if (!nh.found) {
@@ -4222,6 +4238,9 @@ rta_multipath_done:
} else {
/* Kernel supports new style nexthop configuration,
* verify that it is a duplicate and ignore old-style nexthop. */
if (nh.is_via)
return NULL;
if (nh.ifindex != ifindex || memcmp(&nh.gateway, &gateway, addr_len) != 0) {
/* we have a RTA_MULTIPATH attribute that does not agree.
* That seems not right. Error out. */
@@ -4237,7 +4256,7 @@ rta_multipath_done:
* 1 (lo). Of course it does!! */
if (nh.found) {
if (IS_IPv4) {
if (nh.ifindex != 0 || nh.gateway.addr4 != 0) {
if (nh.ifindex != 0 || nh.gateway.addr4 != 0 || nh.is_via) {
/* we only accept kernel to notify about the ifindex/gateway, if it
* is zero. This is only to be a bit forgiving, but we really don't
* know how to handle such routes that have an ifindex. */
@@ -4336,9 +4355,14 @@ rta_multipath_done:
if (tb[RTA_PRIORITY])
obj->ip_route.metric = nla_get_u32(tb[RTA_PRIORITY]);
if (IS_IPv4)
obj->ip4_route.gateway = nh.gateway.addr4;
else
if (IS_IPv4) {
if (nh.is_via) {
obj->ip4_route.via.addr_family = AF_INET6;
obj->ip4_route.via.addr = nh.gateway;
} else {
obj->ip4_route.gateway = nh.gateway.addr4;
}
} else
obj->ip6_route.gateway = nh.gateway.addr6;
if (IS_IPv4)
@@ -5828,7 +5852,24 @@ _nl_msg_new_route(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPObject *ob
/* We currently don't have need for multi-hop routes... */
if (IS_IPv4) {
NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip4_route.gateway);
if (obj->ip4_route.gateway == INADDR_ANY && obj->ip4_route.via.addr_family != AF_UNSPEC) {
struct rtvia *rtvia;
nm_assert(obj->ip4_route.via.addr_family == AF_INET6);
rtvia = nla_data(nla_reserve(
msg,
RTA_VIA,
sizeof(*rtvia) + nm_utils_addr_family_to_size(obj->ip4_route.via.addr_family)));
if (!rtvia)
goto nla_put_failure;
rtvia->rtvia_family = obj->ip4_route.via.addr_family;
memcpy(rtvia->rtvia_addr,
obj->ip4_route.via.addr.addr_ptr,
nm_utils_addr_family_to_size(obj->ip4_route.via.addr_family));
} else {
NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip4_route.gateway);
}
} else {
if (!IN6_IS_ADDR_UNSPECIFIED(&obj->ip6_route.gateway))
NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip6_route.gateway);

View File

@@ -7199,7 +7199,7 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route,
{
char *buf0;
char s_network[INET_ADDRSTRLEN];
char s_gateway[INET_ADDRSTRLEN];
char s_gateway[INET6_ADDRSTRLEN];
char s_pref_src[INET_ADDRSTRLEN];
char str_dev[30];
char str_mss[32];
@@ -7228,10 +7228,13 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route,
inet_ntop(AF_INET, &route->network, s_network, sizeof(s_network));
if (route->gateway == 0)
s_gateway[0] = '\0';
else
if (route->gateway != INADDR_ANY) {
inet_ntop(AF_INET, &route->gateway, s_gateway, sizeof(s_gateway));
} else if (route->via.addr_family == AF_INET6) {
inet_ntop(AF_INET6, route->via.addr.addr_ptr, s_gateway, sizeof(s_gateway));
} else {
s_gateway[0] = '\0';
}
nm_strbuf_append(
&buf,
@@ -8967,6 +8970,9 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
nm_hash_update_vals(h,
obj->ifindex,
n_nexthops,
obj->via.addr_family,
obj->via.addr_family == AF_INET6 ? obj->via.addr.addr6
: in6addr_any,
obj->gateway,
_ip4_route_weight_normalize(n_nexthops, obj->weight, FALSE));
}
@@ -9117,6 +9123,10 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
NM_CMP_FIELD(a, b, ifindex);
NM_CMP_FIELD(a, b, gateway);
NM_CMP_FIELD(a, b, via.addr_family);
if (a->via.addr_family == AF_INET6) {
NM_CMP_FIELD_IN6ADDR(a, b, via.addr.addr6);
}
n_nexthops = nm_platform_ip4_route_get_n_nexthops(a);
NM_CMP_DIRECT(n_nexthops, nm_platform_ip4_route_get_n_nexthops(b));
NM_CMP_DIRECT(_ip4_route_weight_normalize(n_nexthops, a->weight, FALSE),
@@ -9142,6 +9152,10 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
NM_CMP_FIELD_UNSAFE(a, b, metric_any);
NM_CMP_FIELD(a, b, metric);
NM_CMP_FIELD(a, b, gateway);
NM_CMP_FIELD(a, b, via.addr_family);
if (a->via.addr_family == AF_INET6) {
NM_CMP_FIELD_IN6ADDR(a, b, via.addr.addr6);
}
if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
n_nexthops = nm_platform_ip4_route_get_n_nexthops(a);
NM_CMP_DIRECT(n_nexthops, nm_platform_ip4_route_get_n_nexthops(b));

View File

@@ -443,6 +443,12 @@ struct _NMPlatformIP4Route {
* the first hop. */
in_addr_t gateway;
/* RTA_VIA. Part of the primary key for a route. Allows a gateway for a
* route to exist in a different address family.
* Only valid if: n_nexthops == 1, gateway == 0, via.family != AF_UNSPEC
*/
NMIPAddrTyped via;
/* RTA_PREFSRC (called "src" by iproute2).
*
* pref_src is part of the ID of an IPv4 route. When deleting a route,
@@ -2404,6 +2410,14 @@ nm_platform_ip_route_get_gateway(int addr_family, const NMPlatformIPRoute *route
return &((NMPlatformIP6Route *) route)->gateway;
}
static inline const NMIPAddrTyped *
nm_platform_ip4_route_get_via(const NMPlatformIP4Route *route)
{
nm_assert(route);
return &route->via;
}
static inline gconstpointer
nm_platform_ip_route_get_pref_src(int addr_family, const NMPlatformIPRoute *route)
{