diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c index 78301ffec6..5baceb51f8 100644 --- a/src/core/platform/tests/test-route.c +++ b/src/core/platform/tests/test-route.c @@ -2408,6 +2408,82 @@ test_nexthop_add(void) /*****************************************************************************/ +static void +test_ip6_route_with_nexthop(void) +{ + const int ifindex = NMTSTP_ENV1_IFINDEXES[0]; + NMPObject obj; + int r; + const NMPlatformIP6Route *r6; + struct in6_addr network; + const guint32 metric = 22987; + + g_assert(nm_platform_ip6_address_add(NM_PLATFORM_GET, + ifindex, + nmtst_inet6_from_string("fe80::1"), + 64, + in6addr_any, + NM_PLATFORM_LIFETIME_PERMANENT, + NM_PLATFORM_LIFETIME_PERMANENT, + 0, + NULL)); + + /* Add IPv6 nexthop without gateway (id 300) */ + nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP6_NEXTHOP, NULL); + obj.ip6_nexthop.id = 300; + obj.ip6_nexthop.ifindex = ifindex; + r = nm_platform_ip_nexthop_add(NM_PLATFORM_GET, NMP_NLM_FLAG_ADD, &obj, NULL); + g_assert_cmpint(r, ==, 0); + + /* Add IPv6 nexthop with gateway (id 301) */ + nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP6_NEXTHOP, NULL); + obj.ip6_nexthop.id = 301; + obj.ip6_nexthop.ifindex = ifindex; + obj.ip6_nexthop.gateway = nmtst_inet6_from_string("fe80::99"); + r = nm_platform_ip_nexthop_add(NM_PLATFORM_GET, NMP_NLM_FLAG_ADD, &obj, NULL); + g_assert_cmpint(r, ==, 0); + + /* Add route using nexthop without gateway */ + inet_pton(AF_INET6, "a:b:c:1::", &network); + r = nm_platform_ip6_route_add(NM_PLATFORM_GET, + NMP_NLM_FLAG_REPLACE, + &((NMPlatformIP6Route) { + .ifindex = ifindex, + .network = network, + .plen = 64, + .metric = metric, + .nhid = 300, + })); + g_assert_cmpint(r, ==, 0); + + r6 = nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &network, 64, metric, NULL, 0); + g_assert(r6); + g_assert_cmpint(r6->ifindex, ==, ifindex); + g_assert_cmpint(r6->nhid, ==, 300); + nmtst_assert_ip6_address(&r6->gateway, "::"); + + /* Add route using nexthop with gateway */ + inet_pton(AF_INET6, "a:b:c:2::", &network); + r = nm_platform_ip6_route_add(NM_PLATFORM_GET, + NMP_NLM_FLAG_REPLACE, + &((NMPlatformIP6Route) { + .ifindex = ifindex, + .network = network, + .plen = 64, + .metric = metric, + .nhid = 301, + })); + g_assert_cmpint(r, ==, 0); + + r6 = nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &network, 64, metric, NULL, 0); + g_assert(r6); + g_assert_cmpint(r6->ifindex, ==, ifindex); + g_assert_cmpint(r6->nhid, ==, 301); + nmtst_assert_ip6_address(&r6->gateway, "fe80::99"); +} + +/*****************************************************************************/ + static void _ensure_onlink_routes(void) { @@ -2686,6 +2762,7 @@ _nmtstp_setup_tests(void) if (nmtstp_is_root_test()) { add_test_func_with_if2("/route/nexthop/dump", test_nexthop_dump); add_test_func("/route/nexthop/add", test_nexthop_add); + add_test_func("/route/ip6_with_nexthop", test_ip6_route_with_nexthop); } if (nmtstp_is_root_test()) { diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index 95200182ec..9fb69a0378 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -4015,6 +4015,7 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter [RTA_PRIORITY] = {.type = NLA_U32}, [RTA_PREF] = {.type = NLA_U8}, [RTA_FLOW] = {.type = NLA_U32}, + [RTA_NH_ID] = {.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}, @@ -4052,6 +4053,7 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter guint32 mtu = 0; guint32 rto_min = 0; guint32 lock = 0; + guint32 nhid = 0; gboolean quickack = FALSE; gboolean rto_min_set = FALSE; @@ -4097,7 +4099,12 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter if (rtm->rtm_dst_len > (IS_IPv4 ? 32 : 128)) return NULL; - if (tb[RTA_MULTIPATH]) { + if (tb[RTA_NH_ID] && !IS_IPv4) { + /* we only support NHID for IPv6 at the moment */ + nhid = nla_get_u32(tb[RTA_NH_ID]); + } + + if (nhid == 0 && tb[RTA_MULTIPATH]) { size_t tlen; struct rtnexthop *rtnh; guint idx; @@ -4206,7 +4213,7 @@ rta_multipath_done: return nm_assert_unreachable_val(NULL); } - if (tb[RTA_OIF] || tb[RTA_GATEWAY] || tb[RTA_FLOW] || tb[RTA_VIA]) { + if (nhid == 0 && (tb[RTA_OIF] || tb[RTA_GATEWAY] || tb[RTA_FLOW] || tb[RTA_VIA])) { int ifindex = 0; NMIPAddr gateway = {}; @@ -4252,6 +4259,15 @@ rta_multipath_done: } } + if (nhid != 0) { + if (tb[RTA_OIF]) { + nh.ifindex = nla_get_u32(tb[RTA_OIF]); + nh.found = TRUE; + } + if (_check_addr_or_return_null(tb, RTA_GATEWAY, addr_len)) + memcpy(&nh.gateway, nla_data(tb[RTA_GATEWAY]), addr_len); + } + if (nm_platform_route_type_is_nodev(rtm->rtm_type)) { /* These routes are special. They don't have an device/ifindex. * @@ -4388,6 +4404,9 @@ rta_multipath_done: obj->ip6_route.src_plen = rtm->rtm_src_len; } + if (!IS_IPv4) + obj->ip6_route.nhid = nhid; + obj->ip_route.mss = mss; obj->ip_route.window = window; obj->ip_route.cwnd = cwnd; @@ -5991,10 +6010,14 @@ _nl_msg_new_route(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPObject *ob NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip4_route.gateway); } } else { - if (!IN6_IS_ADDR_UNSPECIFIED(&obj->ip6_route.gateway)) + if (obj->ip6_route.nhid != 0) { + NLA_PUT_U32(msg, RTA_NH_ID, obj->ip6_route.nhid); + } else if (!IN6_IS_ADDR_UNSPECIFIED(&obj->ip6_route.gateway)) NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip6_route.gateway); } - NLA_PUT_U32(msg, RTA_OIF, obj->ip_route.ifindex); + + if (IS_IPv4 || obj->ip6_route.nhid == 0) + NLA_PUT_U32(msg, RTA_OIF, obj->ip_route.ifindex); if (!IS_IPv4 && obj->ip6_route.rt_pref != NM_ICMPV6_ROUTER_PREF_MEDIUM) NLA_PUT_U8(msg, RTA_PREF, obj->ip6_route.rt_pref); diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 8ecadea16c..857123d8cd 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -7612,33 +7612,37 @@ nm_platform_ip6_nexthop_to_string(const NMPlatformIP6NextHop *nexthop, char *buf const char * nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsize len) { - char s_network[INET6_ADDRSTRLEN]; - char s_gateway[INET6_ADDRSTRLEN]; - char s_pref_src[INET6_ADDRSTRLEN]; - char s_src_all[INET6_ADDRSTRLEN + 40]; - char s_src[INET6_ADDRSTRLEN]; - char str_type[30]; - char str_table[30]; - char str_pref[40]; - char str_pref2[30]; - char str_dev[30]; - char str_mss[32]; - char s_source[50]; - 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_metric[30]; + char s_network[INET6_ADDRSTRLEN]; + char s_gateway[INET6_ADDRSTRLEN]; + char s_pref_src[INET6_ADDRSTRLEN]; + char s_src_all[INET6_ADDRSTRLEN + 40]; + char s_src[INET6_ADDRSTRLEN]; + char str_type[30]; + char str_table[30]; + char str_pref[40]; + char str_pref2[30]; + char str_dev[30]; + char str_mss[32]; + char s_source[50]; + 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_metric[30]; + gboolean has_nhid = FALSE; if (!nm_utils_to_string_buffer_init_null(route, &buf, &len)) return buf; inet_ntop(AF_INET6, &route->network, s_network, sizeof(s_network)); - if (IN6_IS_ADDR_UNSPECIFIED(&route->gateway)) + if (route->nhid) { + g_snprintf(s_gateway, sizeof(s_gateway), " nhid %u", route->nhid); + has_nhid = TRUE; + } else if (IN6_IS_ADDR_UNSPECIFIED(&route->gateway)) s_gateway[0] = '\0'; else inet_ntop(AF_INET6, &route->gateway, s_gateway, sizeof(s_gateway)); @@ -7682,7 +7686,7 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz : ""), s_network, route->plen, - s_gateway[0] ? " via " : "", + !has_nhid && s_gateway[0] ? " via " : "", s_gateway, _to_string_dev(str_dev, route->ifindex), route->metric_any @@ -9459,23 +9463,25 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj, *nm_ip6_addr_clear_host_address(&a1, &obj->network, obj->plen), obj->plen, obj->metric, + obj->nhid, *nm_ip6_addr_clear_host_address(&a2, &obj->src, obj->src_plen), obj->src_plen, NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any), /* on top of WEAK_ID: */ - obj->ifindex, - obj->gateway); + obj->nhid == 0 ? obj->ifindex : 0, + obj->nhid == 0 ? obj->gateway : in6addr_any); break; case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY: nm_hash_update_vals( h, obj->type_coerced, nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)), - obj->ifindex, *nm_ip6_addr_clear_host_address(&a1, &obj->network, obj->plen), obj->plen, obj->metric, - obj->gateway, + obj->nhid, + obj->nhid == 0 ? obj->ifindex : 0, + obj->nhid == 0 ? obj->gateway : in6addr_any, obj->pref_src, *nm_ip6_addr_clear_host_address(&a2, &obj->src, obj->src_plen), obj->src_plen, @@ -9504,10 +9510,11 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj, nm_hash_update_vals(h, obj->type_coerced, obj->table_coerced, - obj->ifindex, obj->network, obj->metric, - obj->gateway, + obj->nhid, + obj->nhid == 0 ? obj->ifindex : 0, + obj->nhid == 0 ? obj->gateway : in6addr_any, obj->pref_src, obj->src, obj->src_plen, @@ -9557,9 +9564,12 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a, NM_CMP_DIRECT_IP6_ADDR_SAME_PREFIX(&a->src, &b->src, NM_MIN(a->src_plen, b->src_plen)); NM_CMP_FIELD(a, b, src_plen); if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) { - NM_CMP_FIELD(a, b, ifindex); NM_CMP_FIELD(a, b, type_coerced); - NM_CMP_FIELD_IN6ADDR(a, b, gateway); + NM_CMP_FIELD(a, b, nhid); + if (a->nhid == 0) { + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD_IN6ADDR(a, b, gateway); + } } break; case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY: @@ -9571,7 +9581,6 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a, nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b))); } else NM_CMP_FIELD(a, b, table_coerced); - NM_CMP_FIELD(a, b, ifindex); if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) NM_CMP_DIRECT_IP6_ADDR_SAME_PREFIX(&a->network, &b->network, NM_MIN(a->plen, b->plen)); else @@ -9579,7 +9588,11 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a, NM_CMP_FIELD(a, b, plen); NM_CMP_FIELD_UNSAFE(a, b, metric_any); NM_CMP_FIELD(a, b, metric); - NM_CMP_FIELD_IN6ADDR(a, b, gateway); + NM_CMP_FIELD(a, b, nhid); + if (a->nhid == 0) { + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD_IN6ADDR(a, b, gateway); + } NM_CMP_FIELD_IN6ADDR(a, b, pref_src); if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { NM_CMP_DIRECT_IP6_ADDR_SAME_PREFIX(&a->src, &b->src, NM_MIN(a->src_plen, b->src_plen)); diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index 338e621642..16bb920c5f 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -531,6 +531,22 @@ struct _NMPlatformIP6Route { * The type is guint8 to keep the struct size small. But the values are compatible with * the NMIcmpv6RouterPref enum. */ guint8 rt_pref; + + /* RTA_NH_ID. The unique id of the nexthop object. + * + * When sending a route with a nexthop to the kernel, the ifindex + * and gateway must be unset, otherwise the route will be + * rejected. When the kernel sends notifications to userspace it + * copies the ifindex and the gateway from the nexthop into the + * route. + * + * In a route platform object, the ifindex and gateway are ignored + * if the routes has a nexthop. However, in the platform cache, routes + * with nexthops always have also the ifindex and the gateway set. In + * particular, the ifindex must be set when creating new synthetic + * route because the platform code needs it to properly track the object. + */ + guint32 nhid; } _nm_alignas(NMPlatformObject); typedef union {