diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index f3f09db18c..f2b5448dd3 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -13447,6 +13447,9 @@ activate_stage3_ip_config_for_addr_family(NMDevice *self, int addr_family) * addresses and routes on activation. */ if (ip_ifindex > 0) { + nm_platform_ip_nexthop_flush(nm_device_get_platform(self), + AF_INET6, + ip_ifindex); nm_platform_ip_route_flush(nm_device_get_platform(self), AF_INET6, ip_ifindex); diff --git a/src/core/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c index 9d8f61c55c..9018298169 100644 --- a/src/core/devices/wwan/nm-modem.c +++ b/src/core/devices/wwan/nm-modem.c @@ -1237,6 +1237,7 @@ deactivate_cleanup(NMModem *self, NMDevice *device, gboolean stop_ppp_manager) if (ifindex > 0) { NMPlatform *platform = nm_device_get_platform(device); + nm_platform_ip_nexthop_flush(platform, AF_UNSPEC, ifindex); nm_platform_ip_route_flush(platform, AF_UNSPEC, ifindex); nm_platform_ip_address_flush(platform, AF_UNSPEC, ifindex); nm_platform_link_change_flags(platform, ifindex, IFF_UP, FALSE); diff --git a/src/core/ndisc/nm-ndisc.c b/src/core/ndisc/nm-ndisc.c index ed34f088a0..2016408801 100644 --- a/src/core/ndisc/nm-ndisc.c +++ b/src/core/ndisc/nm-ndisc.c @@ -18,6 +18,7 @@ #include "nm-l3-config-data.h" #include "nm-l3cfg.h" #include "nm-ndisc-private.h" +#include "nm-netns.h" #include "nm-setting-ip6-config.h" #include "nm-utils.h" @@ -105,6 +106,77 @@ NM_UTILS_LOOKUP_STR_DEFINE(nm_ndisc_dhcp_level_to_string, /*****************************************************************************/ +#define NEXTHOP_ID_RETRIES 400 + +static guint32 +nexthop_id_alloc(NMNDisc *ndisc, + const struct in6_addr *dest, + guint plen, + const struct in6_addr *gateway) +{ + NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc); + NMNetns *netns = nm_l3cfg_get_netns(priv->config.l3cfg); + const char *ifname = nm_l3cfg_get_ifname(priv->config.l3cfg, FALSE); + NMPlatform *platform = nm_l3cfg_get_platform(priv->config.l3cfg); + int ifindex = nm_l3cfg_get_ifindex(priv->config.l3cfg); + nm_auto_nmpobj NMPObject *obj = NULL; + CSipHash state; + guint64 id64; + guint32 id; + guint i; + + /* Determine a stable nexthop ID by hashing the interface name, the destination + * and the gateway. We set the high bit to decrease the chance of collisions with + * external (manually added) nexthops. */ + c_siphash_init(&state, NM_HASH_SEED_16_U64(725697701u)); + c_siphash_append(&state, (const uint8_t *) ifname, strlen(ifname) + 1); + c_siphash_append(&state, (const uint8_t *) dest, sizeof(struct in6_addr)); + c_siphash_append(&state, (const uint8_t *) &plen, sizeof(guint)); + c_siphash_append(&state, (const uint8_t *) gateway, sizeof(struct in6_addr)); + id64 = c_siphash_finalize(&state); + id = ((guint32) (id64 >> 32u)) | (1u << 31); + + for (i = 0; i < NEXTHOP_ID_RETRIES; i++, id++) { + if (i < NEXTHOP_ID_RETRIES * 3 / 4) { + id |= (1u << 31); + } else { + /* After many collisions, start probing random ids */ + id = (guint32) nm_random_u64_range(1u << 31, G_MAXUINT32); + } + + if (nm_netns_nexthop_id_is_reserved(netns, id)) + continue; + + if (nm_platform_ip_nexthop_get(platform, id, &obj)) { + /* The id already exists in platform. We can reuse it only + * if it's an IPv6 RA nexthop on the same interface. */ + if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP6_NEXTHOP + || obj->ip6_nexthop.nh_source != NM_IP_CONFIG_SOURCE_RTPROT_RA + || obj->ip6_nexthop.ifindex != ifindex) + continue; + } + + nm_netns_nexthop_id_reserve(netns, id, ndisc); + return id; + } + + return 0; +} + +static void +_nexthop_id_release_one(NMNDisc *ndisc, guint32 nexthop_id) +{ + NMNDiscPrivate *priv; + + if (nexthop_id == 0) + return; + + priv = NM_NDISC_GET_PRIVATE(ndisc); + nm_netns_nexthop_id_release(nm_l3cfg_get_netns(priv->config.l3cfg), nexthop_id); +} + +/*****************************************************************************/ + NML3ConfigData * nm_ndisc_data_to_l3cd(NMDedupMultiIndex *multi_idx, int ifindex, @@ -172,9 +244,6 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *multi_idx, } if (rdata->gateways_n > 0) { - guint metric_offset = 0; - NMIcmpv6RouterPref prev_pref = NM_ICMPV6_ROUTER_PREF_INVALID; - NMPlatformIP6Route r = { .rt_source = NM_IP_CONFIG_SOURCE_NDISC, .ifindex = ifindex, @@ -185,24 +254,38 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *multi_idx, }; for (i = 0; i < rdata->gateways_n; i++) { - /* If we add multiple default routes with the same metric and - * different preferences, kernel merges them into a single ECMP - * route, with overall preference equal to the preference of the - * first route added. Therefore, the preference of individual routes - * is not respected. - * To avoid that, add routes with different metrics if they have - * different preferences, so that they are not merged together. Here - * the gateways are already ordered by increasing preference. */ - if (i != 0 && rdata->gateways[i].preference != prev_pref) { - metric_offset++; - } + NMPlatformIP6NextHop nh; - prev_pref = rdata->gateways[i].preference; - r.metric = metric_offset; - r.gateway = rdata->gateways[i].address; r.rt_pref = rdata->gateways[i].preference; nm_assert((NMIcmpv6RouterPref) r.rt_pref == rdata->gateways[i].preference); + + /* If we add multiple routes with the same destination (in this case, the + * default route) and the same metric, the kernel merges them into a single + * ECMP route, which is forbidden by RFCs as it breaks NUD and other use cases. + * Use nexthop objects to avoid this merging behavior. + * + * We could use nexthops only when there are multiple default routes on this + * interface. But that is not enough, because there can be multiple profiles + * with the same ipv6.route-metric value, and their default routes would still + * be merged. We need to always use nexthops. + */ + + if (rdata->gateways[i].nexthop_id == 0) { + /* The nexthop id could not be reserved and we already emitted a warning */ + continue; + } + + nh = (NMPlatformIP6NextHop) { + .ifindex = ifindex, + .nh_source = NM_IP_CONFIG_SOURCE_NDISC, + .gateway = rdata->gateways[i].address, + .id = rdata->gateways[i].nexthop_id, + }; + + r.nhid = nh.id; + r.gateway = nh.gateway; nm_l3_config_data_add_route_6(l3cd, &r); + nm_l3_config_data_add_nexthop(l3cd, AF_INET6, NULL, (const NMPlatformIPNextHop *) &nh); } } @@ -465,23 +548,38 @@ nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new_item, gint64 now_ { NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata; guint i; - guint insert_idx = G_MAXUINT; + guint insert_idx = G_MAXUINT; + guint32 old_nexthop_id = 0; + NMNDiscGateway gw; for (i = 0; i < rdata->gateways->len;) { NMNDiscGateway *item = &nm_g_array_index(rdata->gateways, NMNDiscGateway, i); if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) { if (new_item->expiry_msec <= now_msec) { + _nexthop_id_release_one(ndisc, item->nexthop_id); g_array_remove_index(rdata->gateways, i); _ASSERT_data_gateways(rdata); return TRUE; } if (item->preference != new_item->preference) { + /* Preference changed: save the nexthop ID so that we can + * reuse it when re-inserting at the correct position. */ + old_nexthop_id = item->nexthop_id; g_array_remove_index(rdata->gateways, i); continue; } + if (item->nexthop_id == 0) { + /* We failed to allocate the nexthop id previously; retry. */ + item->nexthop_id = nexthop_id_alloc(ndisc, &in6addr_any, 0, &new_item->address); + if (item->nexthop_id > 0) { + item->expiry_msec = new_item->expiry_msec; + return TRUE; + } + } + if (item->expiry_msec == new_item->expiry_msec) return FALSE; @@ -505,9 +603,26 @@ nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new_item, gint64 now_ if (new_item->expiry_msec <= now_msec) return FALSE; + /* Make a copy of the gateway and assign a nexthop id, reusing the existing + * one if possible */ + gw = *new_item; + if (old_nexthop_id != 0) { + gw.nexthop_id = old_nexthop_id; + } else { + gw.nexthop_id = nexthop_id_alloc(ndisc, &in6addr_any, 0, &new_item->address); + if (gw.nexthop_id == 0) { + char buf[INET6_ADDRSTRLEN]; + + _LOGW("failed to find a free nexthop id for gateway %s", + nm_inet6_ntop(&new_item->address, buf)); + return FALSE; + } + } + g_array_insert_val(rdata->gateways, insert_idx == G_MAXUINT ? rdata->gateways->len : insert_idx, - *new_item); + gw); + _ASSERT_data_gateways(rdata); return TRUE; } @@ -1330,6 +1445,9 @@ nm_ndisc_stop(NMNDisc *ndisc) NM_NDISC_GET_CLASS(ndisc)->stop(ndisc); + /* Release all nexthop IDs reserved by this ndisc instance. */ + nm_netns_nexthop_id_release_all(nm_l3cfg_get_netns(priv->config.l3cfg), ndisc); + rdata = &priv->rdata; g_array_set_size(rdata->gateways, 0); @@ -1442,9 +1560,10 @@ _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed) for (i = 0; i < rdata->gateways->len; i++) { const NMNDiscGateway *gateway = &nm_g_array_index(rdata->gateways, NMNDiscGateway, i); - _LOGD(" gateway %s pref %s exp %s", + _LOGD(" gateway %s pref %s nhid %u exp %s", nm_inet6_ntop(&gateway->address, addrstr), nm_icmpv6_router_pref_to_string(gateway->preference, str_pref, sizeof(str_pref)), + gateway->nexthop_id, get_exp(str_exp, now_msec, gateway)); } for (i = 0; i < rdata->addresses->len; i++) { @@ -1521,8 +1640,11 @@ clean_gateways(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint6 arr = &nm_g_array_first(rdata->gateways, NMNDiscGateway); for (i = 0, j = 0; i < rdata->gateways->len; i++) { - if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec)) + if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec)) { + /* Gateway expired. Release its nexthop ID. */ + _nexthop_id_release_one(ndisc, arr[i].nexthop_id); continue; + } if (i != j) arr[j] = arr[i]; j++; @@ -1533,8 +1655,14 @@ clean_gateways(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint6 g_array_set_size(rdata->gateways, j); } - if (_array_set_size_max(rdata->gateways, _SIZE_MAX_GATEWAYS)) + if (rdata->gateways->len > _SIZE_MAX_GATEWAYS) { + for (i = _SIZE_MAX_GATEWAYS; i < rdata->gateways->len; i++) + _nexthop_id_release_one( + ndisc, + nm_g_array_index(rdata->gateways, NMNDiscGateway, i).nexthop_id); + g_array_set_size(rdata->gateways, _SIZE_MAX_GATEWAYS); *changed |= NM_NDISC_CONFIG_GATEWAYS; + } _ASSERT_data_gateways(rdata); } @@ -1596,7 +1724,7 @@ clean_routes(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 g_array_set_size(rdata->routes, j); } - if (_array_set_size_max(rdata->gateways, _SIZE_MAX_ROUTES)) + if (_array_set_size_max(rdata->routes, _SIZE_MAX_ROUTES)) *changed |= NM_NDISC_CONFIG_ROUTES; } @@ -1665,7 +1793,7 @@ clean_dns_servers(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gi g_array_set_size(rdata->dns_servers, j); } - if (_array_set_size_max(rdata->gateways, _SIZE_MAX_DNS_SERVERS)) + if (_array_set_size_max(rdata->dns_servers, _SIZE_MAX_DNS_SERVERS)) *changed |= NM_NDISC_CONFIG_DNS_SERVERS; } @@ -1700,7 +1828,7 @@ clean_dns_domains(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gi g_array_set_size(rdata->dns_domains, j); } - if (_array_set_size_max(rdata->gateways, _SIZE_MAX_DNS_DOMAINS)) + if (_array_set_size_max(rdata->dns_domains, _SIZE_MAX_DNS_DOMAINS)) *changed |= NM_NDISC_CONFIG_DNS_DOMAINS; } @@ -2065,6 +2193,8 @@ finalize(GObject *object) NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc); NMNDiscDataInternal *rdata = &priv->rdata; + nm_netns_nexthop_id_release_all(nm_l3cfg_get_netns(priv->config.l3cfg), ndisc); + g_array_unref(rdata->gateways); g_array_unref(rdata->addresses); g_array_unref(rdata->routes); diff --git a/src/core/ndisc/nm-ndisc.h b/src/core/ndisc/nm-ndisc.h index 5b2efde569..6d75417b6c 100644 --- a/src/core/ndisc/nm-ndisc.h +++ b/src/core/ndisc/nm-ndisc.h @@ -100,6 +100,7 @@ typedef struct _NMNDiscGateway { struct in6_addr address; gint64 expiry_msec; NMIcmpv6RouterPref preference; + guint32 nexthop_id; } NMNDiscGateway; typedef struct _NMNDiscAddress { diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index 0b2804fa72..ad4c408a25 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -42,6 +42,14 @@ struct _NML3ConfigData { DedupMultiIdxType idx_routes_x[2]; }; + union { + struct { + DedupMultiIdxType idx_nexthops_6; + DedupMultiIdxType idx_nexthops_4; + }; + DedupMultiIdxType idx_nexthops_x[2]; + }; + union { struct { const NMPObject *best_default_route_6; @@ -455,6 +463,17 @@ nm_l3_config_data_log(const NML3ConfigData *self, i++; } + nm_l3_config_data_iter_obj_for_each (&iter, + self, + &obj, + NMP_OBJECT_TYPE_IP_NEXTHOP(IS_IPv4)) { + _L("nexthop%c[%u]: %s", + nm_utils_addr_family_to_char(addr_family), + i, + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + i++; + } + if (self->route_table_sync_x[IS_IPv4] != NM_IP_ROUTE_TABLE_SYNC_MODE_NONE) { _L("route-table-sync-mode%c: %d", nm_utils_addr_family_to_char(addr_family), @@ -777,6 +796,8 @@ nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex, NMIPConfigSourc _idx_type_init(&self->idx_addresses_6, NMP_OBJECT_TYPE_IP6_ADDRESS); _idx_type_init(&self->idx_routes_4, NMP_OBJECT_TYPE_IP4_ROUTE); _idx_type_init(&self->idx_routes_6, NMP_OBJECT_TYPE_IP6_ROUTE); + _idx_type_init(&self->idx_nexthops_4, NMP_OBJECT_TYPE_IP4_NEXTHOP); + _idx_type_init(&self->idx_nexthops_6, NMP_OBJECT_TYPE_IP6_NEXTHOP); return self; } @@ -844,6 +865,8 @@ nm_l3_config_data_unref(const NML3ConfigData *self) nm_dedup_multi_index_remove_idx(mutable->multi_idx, &mutable->idx_addresses_6.parent); nm_dedup_multi_index_remove_idx(mutable->multi_idx, &mutable->idx_routes_4.parent); nm_dedup_multi_index_remove_idx(mutable->multi_idx, &mutable->idx_routes_6.parent); + nm_dedup_multi_index_remove_idx(mutable->multi_idx, &mutable->idx_nexthops_4.parent); + nm_dedup_multi_index_remove_idx(mutable->multi_idx, &mutable->idx_nexthops_6.parent); nmp_object_unref(mutable->best_default_route_4); nmp_object_unref(mutable->best_default_route_6); @@ -938,6 +961,40 @@ nm_l3_config_data_lookup_route(const NML3ConfigData *self, nmp_object_stackinit(&obj_stack, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4), needle)); } +const NMPlatformIP4NextHop * +nm_l3_config_data_lookup_nexthop4(const NML3ConfigData *self, guint32 id) +{ + const NMDedupMultiEntry *head; + NMPObject obj_stack; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + nmp_object_stackinit_id_ip4_nexthop(&obj_stack, id); + + head = nm_l3_config_data_lookup_obj(self, &obj_stack); + if (!head) + return NULL; + + return NMP_OBJECT_CAST_IP4_NEXTHOP(head->obj); +} + +const NMPlatformIP6NextHop * +nm_l3_config_data_lookup_nexthop6(const NML3ConfigData *self, guint32 id) +{ + const NMDedupMultiEntry *head; + NMPObject obj_stack; + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + nmp_object_stackinit_id_ip6_nexthop(&obj_stack, id); + + head = nm_l3_config_data_lookup_obj(self, &obj_stack); + if (!head) + return NULL; + + return NMP_OBJECT_CAST_IP6_NEXTHOP(head->obj); +} + const NMDedupMultiIdxType * nm_l3_config_data_lookup_index(const NML3ConfigData *self, NMPObjectType obj_type) { @@ -952,6 +1009,10 @@ nm_l3_config_data_lookup_index(const NML3ConfigData *self, NMPObjectType obj_typ return &self->idx_routes_4.parent; case NMP_OBJECT_TYPE_IP6_ROUTE: return &self->idx_routes_6.parent; + case NMP_OBJECT_TYPE_IP4_NEXTHOP: + return &self->idx_nexthops_4.parent; + case NMP_OBJECT_TYPE_IP6_NEXTHOP: + return &self->idx_nexthops_6.parent; default: return nm_assert_unreachable_val(NULL); } @@ -1168,7 +1229,9 @@ _l3_config_data_add_obj(NMDedupMultiIndex *multi_idx, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ADDRESS, - NMP_OBJECT_TYPE_IP6_ROUTE)); + NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_IP4_NEXTHOP, + NMP_OBJECT_TYPE_IP6_NEXTHOP)); nm_assert((!!obj_new) != (!!pl_new)); if (NM_IN_SET(idx_type->obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)) { @@ -1257,6 +1320,9 @@ _l3_config_data_add_obj(NMDedupMultiIndex *multi_idx, modified = TRUE; } + break; + case NMP_OBJECT_TYPE_IP4_NEXTHOP: + case NMP_OBJECT_TYPE_IP6_NEXTHOP: break; default: nm_assert_not_reached(); @@ -1448,6 +1514,29 @@ nm_l3_config_data_add_route_full(NML3ConfigData *self, return changed; } +gboolean +nm_l3_config_data_add_nexthop(NML3ConfigData *self, + int addr_family, + const NMPObject *obj_new, + const NMPlatformIPNextHop *pl_new) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + + nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE)); + nm_assert_addr_family(addr_family); + nm_assert((!pl_new) != (!obj_new)); + nm_assert(!obj_new || (NMP_OBJECT_GET_ADDR_FAMILY(obj_new) == addr_family)); + + return _l3_config_data_add_obj(self->multi_idx, + &self->idx_nexthops_x[IS_IPv4], + self->ifindex, + obj_new, + (const NMPlatformObject *) pl_new, + NM_L3_CONFIG_ADD_FLAGS_MERGE, + NULL, + NULL); +} + const NMPObject * nm_l3_config_data_get_best_default_route(const NML3ConfigData *self, int addr_family) { @@ -3673,6 +3762,41 @@ nm_l3_config_data_merge(NML3ConfigData *self, #undef _ensure_r } + nm_l3_config_data_iter_obj_for_each (&iter, + src, + &obj, + NMP_OBJECT_TYPE_IP_NEXTHOP(IS_IPv4)) { + const NMPlatformIPNextHop *nh_src = NMP_OBJECT_CAST_IP_NEXTHOP(obj); + NMPlatformIPXNextHop nh; +#define _ensure_a() \ + G_STMT_START \ + { \ + if (nh_src != &nh.nhx) { \ + if (IS_IPv4) \ + nh.nh4 = *NMP_OBJECT_CAST_IP4_NEXTHOP(obj); \ + else \ + nh.nh6 = *NMP_OBJECT_CAST_IP6_NEXTHOP(obj); \ + nh_src = &nh.nhx; \ + } \ + } \ + G_STMT_END + + if (nh_src->ifindex != self->ifindex) { + _ensure_a(); + nh.nhx.ifindex = self->ifindex; + } + + _l3_config_data_add_obj(self->multi_idx, + &self->idx_nexthops_x[IS_IPv4], + self->ifindex, + nh_src == &nh.nhx ? NULL : obj, + (const NMPlatformObject *) (nh_src == &nh.nhx ? nh_src : NULL), + NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE, + NULL, + NULL); +#undef _ensure_r + } + if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) _strv_ptrarray_merge(&self->nameservers_x[IS_IPv4], src->nameservers_x[IS_IPv4]); diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h index b76e11f9b1..5098c4f767 100644 --- a/src/core/nm-l3-config-data.h +++ b/src/core/nm-l3-config-data.h @@ -251,6 +251,12 @@ const NMDedupMultiEntry *nm_l3_config_data_lookup_route(const NML3ConfigData int addr_family, const NMPlatformIPRoute *needle); +const NMPlatformIP4NextHop *nm_l3_config_data_lookup_nexthop4(const NML3ConfigData *self, + guint32 id); + +const NMPlatformIP6NextHop *nm_l3_config_data_lookup_nexthop6(const NML3ConfigData *self, + guint32 id); + const NMDedupMultiHeadEntry *nm_l3_config_data_lookup_objs(const NML3ConfigData *self, NMPObjectType obj_type); @@ -266,6 +272,12 @@ nm_l3_config_data_lookup_routes(const NML3ConfigData *self, int addr_family) return nm_l3_config_data_lookup_objs(self, NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family))); } +static inline const NMDedupMultiHeadEntry * +nm_l3_config_data_lookup_nexthops(const NML3ConfigData *self, int addr_family) +{ + return nm_l3_config_data_lookup_objs(self, NMP_OBJECT_TYPE_IP_NEXTHOP(NM_IS_IPv4(addr_family))); +} + #define nm_l3_config_data_iter_obj_for_each(iter, self, obj, type) \ for (nm_dedup_multi_iter_init((iter), nm_l3_config_data_lookup_objs((self), (type))); \ nm_platform_dedup_multi_iter_next_obj((iter), (obj), (type));) @@ -435,6 +447,11 @@ nm_l3_config_data_add_route(NML3ConfigData *self, NULL); } +gboolean nm_l3_config_data_add_nexthop(NML3ConfigData *self, + int addr_family, + const NMPObject *obj_new, + const NMPlatformIPNextHop *pl_new); + static inline gboolean nm_l3_config_data_add_route_4(NML3ConfigData *self, const NMPlatformIP4Route *rt) { diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 59fc6e4d7f..5f60f86b03 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -319,6 +319,17 @@ typedef struct _NML3CfgPrivate { int clat_socket; #endif /* HAVE_CLAT */ + /* Tracks nexthop IDs that NM has configured, per address family. + * Used to build the nexthop prune list without relying on the + * platform cache or the zombie mechanism. */ + union { + struct { + GHashTable *nexthop_configured_ids_6; + GHashTable *nexthop_configured_ids_4; + }; + GHashTable *nexthop_configured_ids_x[2]; + }; + /* Whether we earlier configured MPTCP endpoints for the interface. */ union { struct { @@ -826,7 +837,9 @@ _nm_n_acd_data_probe_new(NML3Cfg *self, in_addr_t addr, guint32 timeout_msec, gp NMP_OBJECT_TYPE_IP4_ADDRESS, \ NMP_OBJECT_TYPE_IP6_ADDRESS, \ NMP_OBJECT_TYPE_IP4_ROUTE, \ - NMP_OBJECT_TYPE_IP6_ROUTE)); \ + NMP_OBJECT_TYPE_IP6_ROUTE, \ + NMP_OBJECT_TYPE_IP4_NEXTHOP, \ + NMP_OBJECT_TYPE_IP6_NEXTHOP)); \ nm_assert(!_obj_state->os_plobj || _obj_state->os_was_in_platform); \ nm_assert(_obj_state->os_failedobj_expiry_msec != 0 \ || _obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); \ @@ -1189,6 +1202,8 @@ _obj_states_update_all(NML3Cfg *self) NMP_OBJECT_TYPE_IP6_ADDRESS, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_IP4_NEXTHOP, + NMP_OBJECT_TYPE_IP6_NEXTHOP, }; ObjStateData *obj_state; int i; @@ -1321,6 +1336,23 @@ _commit_collect_addresses(NML3Cfg *self, int addr_family, NML3CfgCommitType comm (gpointer) &sync_filter_data); } +static GPtrArray * +_commit_collect_nexthops(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + const NMDedupMultiHeadEntry *head_entry; + const ObjStatesSyncFilterData sync_filter_data = { + .self = self, + .commit_type = commit_type, + }; + + head_entry = nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited, + NMP_OBJECT_TYPE_IP_NEXTHOP(IS_IPv4)); + return nm_dedup_multi_objs_to_ptr_array_head(head_entry, + _obj_states_sync_filter_predicate, + (gpointer) &sync_filter_data); +} + static void _commit_collect_routes(NML3Cfg *self, int addr_family, @@ -5486,13 +5518,16 @@ _l3_commit_one(NML3Cfg *self, NML3CfgCommitType commit_type, const NML3ConfigData *l3cd_old) { - const int IS_IPv4 = NM_IS_IPv4(addr_family); - gs_unref_ptrarray GPtrArray *addresses = NULL; - gs_unref_ptrarray GPtrArray *routes = NULL; - gs_unref_ptrarray GPtrArray *routes_nodev = NULL; - gs_unref_ptrarray GPtrArray *addresses_prune = NULL; - gs_unref_ptrarray GPtrArray *routes_prune = NULL; - gs_unref_ptrarray GPtrArray *routes_failed = NULL; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + gs_unref_ptrarray GPtrArray *addresses = NULL; + gs_unref_ptrarray GPtrArray *nexthops = NULL; + gs_unref_ptrarray GPtrArray *routes = NULL; + gs_unref_ptrarray GPtrArray *routes_nodev = NULL; + gs_unref_ptrarray GPtrArray *addresses_prune = NULL; + gs_unref_ptrarray GPtrArray *nexthops_prune = NULL; + gs_unref_ptrarray GPtrArray *nexthops_platform = NULL; + gs_unref_ptrarray GPtrArray *routes_prune = NULL; + gs_unref_ptrarray GPtrArray *routes_failed = NULL; NMIPRouteTableSyncMode route_table_sync; char sbuf_commit_type[50]; guint i; @@ -5513,6 +5548,7 @@ _l3_commit_one(NML3Cfg *self, any_dirty = _obj_states_track_mark_dirty(self, TRUE); addresses = _commit_collect_addresses(self, addr_family, commit_type); + nexthops = _commit_collect_nexthops(self, addr_family, commit_type); _commit_collect_routes(self, addr_family, @@ -5594,17 +5630,46 @@ _l3_commit_one(NML3Cfg *self, route_table_sync, routes_old); + nexthops_platform = + nm_platform_ip_nexthop_dump(self->priv.platform, addr_family, self->priv.ifindex); + nexthops_prune = nm_g_ptr_array_ref(nexthops_platform); + _obj_state_zombie_lst_prune_all(self, addr_family); } } else { if (c_list_is_empty(&self->priv.p->blocked_lst_head_x[IS_IPv4])) { + GHashTable *ht = self->priv.p->nexthop_configured_ids_x[IS_IPv4]; + GHashTableIter h_iter; + gpointer key; + _obj_state_zombie_lst_get_prune_lists(self, addr_family, &addresses_prune, &routes_prune); + + /* Unlike addresses and routes, nexthop pruning doesn't use the + * zombie mechanism. On update, build the nexthop prune list from the internally + * tracked set of IDs that NM has configured. */ + g_hash_table_iter_init(&h_iter, ht); + while (g_hash_table_iter_next(&h_iter, &key, NULL)) { + NMPObject *obj; + + obj = nmp_object_new(NMP_OBJECT_TYPE_IP_NEXTHOP(IS_IPv4), NULL); + obj->ip_nexthop.id = GPOINTER_TO_UINT(key); + + if (!nexthops_prune) + nexthops_prune = + g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + g_ptr_array_add(nexthops_prune, obj); + } } } + if (!nexthops_platform) { + nexthops_platform = + nm_platform_ip_nexthop_dump(self->priv.platform, addr_family, self->priv.ifindex); + } + if (self->priv.ifindex == NM_LOOPBACK_IFINDEX) { if (!addresses) { NMPlatformIPXAddress ax; @@ -5639,6 +5704,21 @@ _l3_commit_one(NML3Cfg *self, ? NMP_IP_ADDRESS_SYNC_FLAGS_NONE : NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE); + nm_platform_ip_nexthop_sync(self->priv.platform, + addr_family, + self->priv.ifindex, + nexthops, + nexthops_prune, + nexthops_platform); + + /* Update the set of nexthop IDs we have configured. */ + g_hash_table_remove_all(self->priv.p->nexthop_configured_ids_x[IS_IPv4]); + for (i = 0; i < nm_g_ptr_array_len(nexthops); i++) { + guint32 id = NMP_OBJECT_CAST_IP_NEXTHOP(nexthops->pdata[i])->id; + + g_hash_table_add(self->priv.p->nexthop_configured_ids_x[IS_IPv4], GUINT_TO_POINTER(id)); + } + self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4]--; _nodev_routes_sync(self, addr_family, commit_type, routes_nodev); @@ -6385,6 +6465,9 @@ nm_l3cfg_init(NML3Cfg *self) _obj_state_data_free, NULL); + self->priv.p->nexthop_configured_ids_4 = g_hash_table_new(nm_direct_hash, NULL); + self->priv.p->nexthop_configured_ids_6 = g_hash_table_new(nm_direct_hash, NULL); + nm_prioq_init(&self->priv.p->failedobj_prioq, _failedobj_prioq_cmp); } @@ -6464,6 +6547,9 @@ finalize(GObject *object) nm_assert(c_list_is_empty(&self->priv.p->obj_state_lst_head)); nm_assert(c_list_is_empty(&self->priv.p->obj_state_zombie_lst_head)); + nm_clear_pointer(&self->priv.p->nexthop_configured_ids_4, g_hash_table_destroy); + nm_clear_pointer(&self->priv.p->nexthop_configured_ids_6, g_hash_table_destroy); + if (_nodev_routes_untrack(self, AF_INET)) nmp_global_tracker_sync(self->priv.global_tracker, NMP_OBJECT_TYPE_IP4_ROUTE, FALSE); if (_nodev_routes_untrack(self, AF_INET6)) diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c index faae72670d..1cf9d4d5fb 100644 --- a/src/core/nm-netns.c +++ b/src/core/nm-netns.c @@ -83,6 +83,11 @@ typedef struct { * by IP address. */ GHashTable *watcher_ip_data_idx; + /* Tracks reserved nexthop IDs. Maps guint32 id -> gconstpointer tag. + * This is used to avoid nexthop ID collisions across different + * interfaces/ndisc instances within the same network namespace. */ + GHashTable *nexthop_id_reserved; + CList l3cfg_signal_pending_lst_head; GSource *signal_pending_idle_source; } NMNetnsPrivate; @@ -1521,6 +1526,76 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps /*****************************************************************************/ +gboolean +nm_netns_nexthop_id_is_reserved(NMNetns *self, guint32 id) +{ + NMNetnsPrivate *priv; + + g_return_val_if_fail(NM_IS_NETNS(self), FALSE); + nm_assert(id != 0); + + priv = NM_NETNS_GET_PRIVATE(self); + return g_hash_table_contains(priv->nexthop_id_reserved, GUINT_TO_POINTER(id)); +} + +gboolean +nm_netns_nexthop_id_reserve(NMNetns *self, guint32 id, gconstpointer tag) +{ + NMNetnsPrivate *priv; + gpointer existing_tag; + + g_return_val_if_fail(NM_IS_NETNS(self), FALSE); + nm_assert(id != 0); + nm_assert(tag); + + priv = NM_NETNS_GET_PRIVATE(self); + + if (g_hash_table_lookup_extended(priv->nexthop_id_reserved, + GUINT_TO_POINTER(id), + NULL, + &existing_tag)) { + /* The ID is already reserved. If it's by the same tag, it's + * idempotent; otherwise it's a collision. */ + return existing_tag == tag; + } + + g_hash_table_insert(priv->nexthop_id_reserved, GUINT_TO_POINTER(id), (gpointer) tag); + return TRUE; +} + +void +nm_netns_nexthop_id_release(NMNetns *self, guint32 id) +{ + NMNetnsPrivate *priv; + + g_return_if_fail(NM_IS_NETNS(self)); + nm_assert(id != 0); + + priv = NM_NETNS_GET_PRIVATE(self); + g_hash_table_remove(priv->nexthop_id_reserved, GUINT_TO_POINTER(id)); +} + +void +nm_netns_nexthop_id_release_all(NMNetns *self, gconstpointer tag) +{ + NMNetnsPrivate *priv; + GHashTableIter iter; + gpointer value; + + g_return_if_fail(NM_IS_NETNS(self)); + nm_assert(tag); + + priv = NM_NETNS_GET_PRIVATE(self); + + g_hash_table_iter_init(&iter, priv->nexthop_id_reserved); + while (g_hash_table_iter_next(&iter, NULL, &value)) { + if (value == tag) + g_hash_table_iter_remove(&iter); + } +} + +/*****************************************************************************/ + static void nm_netns_init(NMNetns *self) { @@ -1545,6 +1620,8 @@ nm_netns_init(NMNetns *self) (GDestroyNotify) _watcher_by_tag_destroy, NULL); priv->watcher_ip_data_idx = g_hash_table_new(_watcher_ip_data_hash, _watcher_ip_data_equal); + + priv->nexthop_id_reserved = g_hash_table_new(nm_direct_hash, NULL); } static void @@ -1637,6 +1714,8 @@ dispose(GObject *object) nm_clear_pointer(&priv->watcher_by_tag_idx, g_hash_table_destroy); nm_clear_pointer(&priv->watcher_ip_data_idx, g_hash_table_destroy); + nm_clear_pointer(&priv->nexthop_id_reserved, g_hash_table_destroy); + nm_clear_g_source_inst(&priv->signal_pending_idle_source); if (priv->platform) diff --git a/src/core/nm-netns.h b/src/core/nm-netns.h index e32d5680aa..c2414c7dfd 100644 --- a/src/core/nm-netns.h +++ b/src/core/nm-netns.h @@ -109,4 +109,11 @@ void nm_netns_watcher_add(NMNetns *self, void nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag); void nm_netns_watcher_remove_dirty(NMNetns *self, gconstpointer tag); +/*****************************************************************************/ + +gboolean nm_netns_nexthop_id_is_reserved(NMNetns *self, guint32 id); +gboolean nm_netns_nexthop_id_reserve(NMNetns *self, guint32 id, gconstpointer tag); +void nm_netns_nexthop_id_release(NMNetns *self, guint32 id); +void nm_netns_nexthop_id_release_all(NMNetns *self, gconstpointer tag); + #endif /* __NM_NETNS_H__ */ diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c index fbad2447a9..5baceb51f8 100644 --- a/src/core/platform/tests/test-route.c +++ b/src/core/platform/tests/test-route.c @@ -2239,6 +2239,251 @@ test_mptcp(gconstpointer test_data) /*****************************************************************************/ +static void +test_nexthop_dump(void) +{ + const int ifindex1 = NMTSTP_ENV1_IFINDEXES[0]; + const int ifindex2 = NMTSTP_ENV1_IFINDEXES[1]; + const char *ifname1 = NMTSTP_ENV1_DEVICE_NAME[0]; + const char *ifname2 = NMTSTP_ENV1_DEVICE_NAME[1]; + GPtrArray *result; + const NMPObject *obj; + + nmtstp_run_command_check("ip addr add 1.2.3.0/24 dev %s", ifname1); + nmtstp_run_command_check("ip addr add fe80::1/64 dev %s", ifname1); + nmtstp_run_command_check("ip addr add fe80::2/64 dev %s", ifname2); + + nmtstp_run_command_check("ip nexthop add id 4 dev %s", ifname1); + nmtstp_run_command_check("ip nexthop add id 5 dev %s via 1.2.3.4", ifname1); + nmtstp_run_command_check("ip nexthop add id 6 dev %s", ifname2); + + nmtstp_run_command_check("ip -6 nexthop add id 12345670 dev %s", ifname1); + nmtstp_run_command_check("ip -6 nexthop add id 12345671 dev %s via fe80::11", ifname1); + nmtstp_run_command_check("ip -6 nexthop add id 12345672 dev %s via fe80::12", ifname1); + nmtstp_run_command_check("ip -6 nexthop add id 12345673 dev %s via fe80::11", ifname2); + + /* interface 1, IPv4 */ + result = nm_platform_ip_nexthop_dump(NM_PLATFORM_GET, AF_INET, ifindex1); + g_assert(result); + g_assert_cmpint(result->len, ==, 2); + obj = result->pdata[0]; + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(obj)->id, ==, 4); + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(obj)->ifindex, ==, ifindex1); + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(obj)->gateway, ==, 0); + obj = result->pdata[1]; + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(obj)->id, ==, 5); + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(obj)->ifindex, ==, ifindex1); + nmtst_assert_ip4_address(NMP_OBJECT_CAST_IP4_NEXTHOP(obj)->gateway, "1.2.3.4"); + g_ptr_array_unref(result); + + /* interface 1, IPv6 */ + result = nm_platform_ip_nexthop_dump(NM_PLATFORM_GET, AF_INET6, ifindex1); + g_assert(result); + g_assert_cmpint(result->len, ==, 3); + obj = result->pdata[0]; + g_assert_cmpint(NMP_OBJECT_CAST_IP6_NEXTHOP(obj)->id, ==, 12345670); + g_assert_cmpint(NMP_OBJECT_CAST_IP6_NEXTHOP(obj)->ifindex, ==, ifindex1); + nmtst_assert_ip6_address(&NMP_OBJECT_CAST_IP6_NEXTHOP(obj)->gateway, "::"); + obj = result->pdata[1]; + g_assert_cmpint(NMP_OBJECT_CAST_IP6_NEXTHOP(obj)->id, ==, 12345671); + g_assert_cmpint(NMP_OBJECT_CAST_IP6_NEXTHOP(obj)->ifindex, ==, ifindex1); + nmtst_assert_ip6_address(&NMP_OBJECT_CAST_IP6_NEXTHOP(obj)->gateway, "fe80::11"); + obj = result->pdata[2]; + g_assert_cmpint(NMP_OBJECT_CAST_IP6_NEXTHOP(obj)->id, ==, 12345672); + g_assert_cmpint(NMP_OBJECT_CAST_IP6_NEXTHOP(obj)->ifindex, ==, ifindex1); + nmtst_assert_ip6_address(&NMP_OBJECT_CAST_IP6_NEXTHOP(obj)->gateway, "fe80::12"); + g_ptr_array_unref(result); + + /* interface 2, IPv4 */ + result = nm_platform_ip_nexthop_dump(NM_PLATFORM_GET, AF_INET, ifindex2); + g_assert(result); + g_assert_cmpint(result->len, ==, 1); + obj = result->pdata[0]; + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(obj)->id, ==, 6); + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(obj)->ifindex, ==, ifindex2); + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(obj)->gateway, ==, 0); + g_ptr_array_unref(result); + + /* interface 2, IPv6 */ + result = nm_platform_ip_nexthop_dump(NM_PLATFORM_GET, AF_INET6, ifindex2); + g_assert(result); + g_assert_cmpint(result->len, ==, 1); + obj = result->pdata[0]; + g_assert_cmpint(NMP_OBJECT_CAST_IP6_NEXTHOP(obj)->id, ==, 12345673); + g_assert_cmpint(NMP_OBJECT_CAST_IP6_NEXTHOP(obj)->ifindex, ==, ifindex2); + nmtst_assert_ip6_address(&NMP_OBJECT_CAST_IP6_NEXTHOP(obj)->gateway, "fe80::11"); + g_ptr_array_unref(result); +} + +/*****************************************************************************/ + +static void +test_nexthop_add(void) +{ + const int ifindex = NMTSTP_ENV1_IFINDEXES[0]; + const char *ifname = NMTSTP_ENV1_DEVICE_NAME[0]; + NMPObject obj; + GPtrArray *result; + int r; + + nmtstp_run_command_check("ip addr add 1.2.3.0/24 dev %s", ifname); + nmtstp_run_command_check("ip addr add fe80::1/64 dev %s", ifname); + + /* Add IPv4 nexthop without gateway */ + nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP4_NEXTHOP, NULL); + obj.ip4_nexthop.id = 100; + obj.ip4_nexthop.ifindex = ifindex; + r = nm_platform_ip_nexthop_add(NM_PLATFORM_GET, NMP_NLM_FLAG_ADD, &obj, NULL); + g_assert_cmpint(r, ==, 0); + + /* Add IPv4 nexthop with gateway */ + nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP4_NEXTHOP, NULL); + obj.ip4_nexthop.id = 101; + obj.ip4_nexthop.ifindex = ifindex; + obj.ip4_nexthop.gateway = nmtst_inet4_from_string("1.2.3.4"); + r = nm_platform_ip_nexthop_add(NM_PLATFORM_GET, NMP_NLM_FLAG_ADD, &obj, NULL); + g_assert_cmpint(r, ==, 0); + + /* Add IPv6 nexthop without gateway */ + nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP6_NEXTHOP, NULL); + obj.ip6_nexthop.id = 200; + 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 */ + nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP6_NEXTHOP, NULL); + obj.ip6_nexthop.id = 201; + 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); + + /* Verify IPv4 nexthops via dump */ + result = nm_platform_ip_nexthop_dump(NM_PLATFORM_GET, AF_INET, ifindex); + g_assert(result); + g_assert_cmpint(result->len, ==, 2); + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(result->pdata[0])->id, ==, 100); + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(result->pdata[0])->ifindex, ==, ifindex); + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(result->pdata[0])->gateway, ==, 0); + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(result->pdata[1])->id, ==, 101); + g_assert_cmpint(NMP_OBJECT_CAST_IP4_NEXTHOP(result->pdata[1])->ifindex, ==, ifindex); + nmtst_assert_ip4_address(NMP_OBJECT_CAST_IP4_NEXTHOP(result->pdata[1])->gateway, "1.2.3.4"); + g_ptr_array_unref(result); + + /* Verify IPv6 nexthops via dump */ + result = nm_platform_ip_nexthop_dump(NM_PLATFORM_GET, AF_INET6, ifindex); + g_assert(result); + g_assert_cmpint(result->len, ==, 2); + g_assert_cmpint(NMP_OBJECT_CAST_IP6_NEXTHOP(result->pdata[0])->id, ==, 200); + g_assert_cmpint(NMP_OBJECT_CAST_IP6_NEXTHOP(result->pdata[0])->ifindex, ==, ifindex); + nmtst_assert_ip6_address(&NMP_OBJECT_CAST_IP6_NEXTHOP(result->pdata[0])->gateway, "::"); + g_assert_cmpint(NMP_OBJECT_CAST_IP6_NEXTHOP(result->pdata[1])->id, ==, 201); + g_assert_cmpint(NMP_OBJECT_CAST_IP6_NEXTHOP(result->pdata[1])->ifindex, ==, ifindex); + nmtst_assert_ip6_address(&NMP_OBJECT_CAST_IP6_NEXTHOP(result->pdata[1])->gateway, "fe80::99"); + g_ptr_array_unref(result); + + /* Delete all nexthops */ + g_assert( + nm_platform_object_delete(NM_PLATFORM_GET, nmp_object_stackinit_id_ip4_nexthop(&obj, 100))); + g_assert( + nm_platform_object_delete(NM_PLATFORM_GET, nmp_object_stackinit_id_ip4_nexthop(&obj, 101))); + g_assert( + nm_platform_object_delete(NM_PLATFORM_GET, nmp_object_stackinit_id_ip6_nexthop(&obj, 200))); + g_assert( + nm_platform_object_delete(NM_PLATFORM_GET, nmp_object_stackinit_id_ip6_nexthop(&obj, 201))); + + /* Verify IPv4 nexthops are gone */ + result = nm_platform_ip_nexthop_dump(NM_PLATFORM_GET, AF_INET, ifindex); + g_assert(result); + g_assert_cmpint(result->len, ==, 0); + g_ptr_array_unref(result); + + /* Verify IPv6 nexthops are gone */ + result = nm_platform_ip_nexthop_dump(NM_PLATFORM_GET, AF_INET6, ifindex); + g_assert(result); + g_assert_cmpint(result->len, ==, 0); + g_ptr_array_unref(result); +} + +/*****************************************************************************/ + +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) { @@ -2474,6 +2719,8 @@ void _nmtstp_setup_tests(void) { #define add_test_func(testpath, test_func) nmtstp_env1_add_test_func(testpath, test_func, 1, TRUE) +#define add_test_func_with_if2(testpath, test_func) \ + nmtstp_env1_add_test_func(testpath, test_func, 2, TRUE) #define add_test_func_data(testpath, test_func, arg) \ nmtstp_env1_add_test_func_data(testpath, test_func, arg, 1, TRUE) #define add_test_func_data_with_if2(testpath, test_func, arg) \ @@ -2511,6 +2758,13 @@ _nmtstp_setup_tests(void) add_test_func_data("/route/mptcp/1", test_mptcp, GINT_TO_POINTER(1)); add_test_func_data("/route/mptcp/2", test_mptcp, GINT_TO_POINTER(2)); } + + 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()) { add_test_func_data_with_if2("/route/test_cache_consistency_routes/1", test_cache_consistency_routes, diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index cde96326ca..1aeb1981fe 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -4071,6 +4072,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}, @@ -4108,6 +4110,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; @@ -4153,7 +4156,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; @@ -4262,7 +4270,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 = {}; @@ -4308,6 +4316,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. * @@ -4444,6 +4461,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; @@ -4477,6 +4497,62 @@ rta_multipath_done: return g_steal_pointer(&obj); } +static NMPObject * +_new_from_nl_nexthop(const struct nlmsghdr *nlh, gboolean id_only, gboolean tracked_protos_only) +{ + static const struct nla_policy policy[] = { + [NHA_ID] = {.type = NLA_U32}, + [NHA_OIF] = {.type = NLA_U32}, + [NHA_GATEWAY] = {.maxlen = sizeof(struct in6_addr)}, + }; + const struct nhmsg *nhm; + gboolean IS_IPv4; + struct nlattr *tb[G_N_ELEMENTS(policy)]; + nm_auto_nmpobj NMPObject *obj = NULL; + + if (!nlmsg_valid_hdr(nlh, sizeof(*nhm))) + return NULL; + + nhm = nlmsg_data(nlh); + + if (tracked_protos_only && !NM_IN_SET(nhm->nh_protocol, IP_ROUTE_TRACKED_PROTOCOLS)) { + return NULL; + } + + if (nhm->nh_family == AF_INET) + IS_IPv4 = TRUE; + else if (nhm->nh_family == AF_INET6) + IS_IPv4 = FALSE; + else + return NULL; + + if (nlmsg_parse_arr(nlh, sizeof(*nhm), tb, policy) < 0) + return NULL; + + if (!tb[NHA_ID]) + return NULL; + + obj = nmp_object_new(NMP_OBJECT_TYPE_IP_NEXTHOP(IS_IPv4), NULL); + obj->ip_nexthop.id = nla_get_u32(tb[NHA_ID]); + + if (id_only) + return g_steal_pointer(&obj); + + obj->ip_nexthop.nh_source = nmp_utils_ip_config_source_from_rtprot(nhm->nh_protocol); + if (tb[NHA_OIF]) + obj->ip_nexthop.ifindex = nla_get_u32(tb[NHA_OIF]); + + if (IS_IPv4) { + if (tb[NHA_GATEWAY] && nla_len(tb[NHA_GATEWAY]) == sizeof(in_addr_t)) + obj->ip4_nexthop.gateway = nla_get_u32(tb[NHA_GATEWAY]); + return g_steal_pointer(&obj); + } else { + if (tb[NHA_GATEWAY] && nla_len(tb[NHA_GATEWAY]) == sizeof(struct in6_addr)) + nla_memcpy(&obj->ip6_nexthop.gateway, tb[NHA_GATEWAY], sizeof(struct in6_addr)); + return g_steal_pointer(&obj); + } +} + static NMPObject * _new_from_nl_routing_rule(const struct nlmsghdr *nlh, gboolean id_only) { @@ -4942,6 +5018,10 @@ nmp_object_new_from_nl(NMPlatform *platform, case RTM_DELRULE: case RTM_GETRULE: return _new_from_nl_routing_rule(msghdr, id_only); + case RTM_NEWNEXTHOP: + case RTM_DELNEXTHOP: + case RTM_GETNEXTHOP: + return _new_from_nl_nexthop(msghdr, id_only, FALSE); case RTM_NEWQDISC: case RTM_DELQDISC: case RTM_GETQDISC: @@ -5793,6 +5873,54 @@ ip_route_is_alive(const NMPlatformIPRoute *route) return ip_route_is_tracked(proto, type); } +static struct nl_msg * +_nl_msg_new_nexthop(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPObject *obj) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + const NMPClass *klass = NMP_OBJECT_GET_CLASS(obj); + const gboolean IS_IPv4 = NM_IS_IPv4(klass->addr_family); + struct nhmsg nhm = { + .nh_family = klass->addr_family, + }; + + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), + NMP_OBJECT_TYPE_IP4_NEXTHOP, + NMP_OBJECT_TYPE_IP6_NEXTHOP)); + nm_assert(NM_IN_SET(nlmsg_type, RTM_NEWNEXTHOP, RTM_DELNEXTHOP)); + + msg = nlmsg_alloc_new(0, nlmsg_type, nlmsg_flags); + + if (nlmsg_type == RTM_NEWNEXTHOP) { + /* the protocol must be zero for deletion messages */ + nhm.nh_protocol = nmp_utils_ip_config_source_coerce_to_rtprot(obj->ip_nexthop.nh_source); + } + + if (nlmsg_append_struct(msg, &nhm) < 0) + goto nla_put_failure; + + NLA_PUT_U32(msg, NHA_ID, obj->ip_nexthop.id); + + if (nlmsg_type == RTM_DELNEXTHOP) + goto end; + + if (obj->ip_nexthop.ifindex > 0) + NLA_PUT_U32(msg, NHA_OIF, obj->ip_nexthop.ifindex); + + if (IS_IPv4) { + if (obj->ip4_nexthop.gateway != INADDR_ANY) + NLA_PUT(msg, NHA_GATEWAY, sizeof(in_addr_t), &obj->ip4_nexthop.gateway); + } else { + if (!IN6_IS_ADDR_UNSPECIFIED(&obj->ip6_nexthop.gateway)) + NLA_PUT(msg, NHA_GATEWAY, sizeof(struct in6_addr), &obj->ip6_nexthop.gateway); + } + +end: + return g_steal_pointer(&msg); + +nla_put_failure: + g_return_val_if_reached(NULL); +} + /* Copied and modified from libnl3's build_route_msg() and rtnl_route_build_msg(). */ static struct nl_msg * _nl_msg_new_route(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPObject *obj) @@ -5968,10 +6096,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); @@ -8634,7 +8766,9 @@ do_add_addrroute(NMPlatform *platform, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS, NMP_OBJECT_TYPE_IP4_ROUTE, - NMP_OBJECT_TYPE_IP6_ROUTE)); + NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_IP4_NEXTHOP, + NMP_OBJECT_TYPE_IP6_NEXTHOP)); event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE); @@ -10809,6 +10943,177 @@ ip6_address_delete(NMPlatform *platform, int ifindex, struct in6_addr addr, guin /*****************************************************************************/ +static int +ip_nexthop_add(NMPlatform *platform, NMPNlmFlags flags, NMPObject *obj_stack, char **out_extack_msg) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + + nlmsg = _nl_msg_new_nexthop(RTM_NEWNEXTHOP, flags & NMP_NLM_FLAG_FMASK, obj_stack); + if (!nlmsg) + g_return_val_if_reached(-NME_BUG); + return do_add_addrroute(platform, + obj_stack, + nlmsg, + NM_FLAGS_HAS(flags, NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE), + out_extack_msg); +} + +typedef struct { + GPtrArray *result; + int addr_family; + int ifindex; +} NexthopDumpData; + +static int +_ip_nexthop_dump_parse_cb(const struct nl_msg *msg, void *arg) +{ + NexthopDumpData *data = arg; + nm_auto_nmpobj NMPObject *obj = NULL; + + obj = _new_from_nl_nexthop(nlmsg_hdr(msg), FALSE, TRUE); + if (!obj) + return NL_SKIP; + + g_ptr_array_add(data->result, g_steal_pointer(&obj)); + return NL_OK; +} + +static GPtrArray * +ip_nexthop_dump(NMPlatform *platform, int addr_family, int ifindex) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nl_sock *sk = NULL; + struct nhmsg nhm = { + .nh_family = addr_family, + }; + NexthopDumpData data; + int nle; + + nlmsg = nlmsg_alloc_new(0, RTM_GETNEXTHOP, NLM_F_DUMP); + if (!nlmsg) + g_return_val_if_reached(NULL); + + if (nlmsg_append_struct(nlmsg, &nhm) < 0) + g_return_val_if_reached(NULL); + + NLA_PUT_U32(nlmsg, NHA_OIF, ifindex); + + nle = nl_socket_new(&sk, NETLINK_ROUTE, NL_SOCKET_FLAGS_DISABLE_MSG_PEEK, 0, 0); + if (nle < 0) { + _LOGD("nexthop-dump: error opening socket: %s (%d)", nm_strerror(nle), nle); + return NULL; + } + + nle = nl_send_auto(sk, nlmsg); + if (nle < 0) { + _LOGD("nexthop-dump: failed sending request: %s (%d)", nm_strerror(nle), nle); + nl_socket_free(sk); + return NULL; + } + + data = (NexthopDumpData) { + .result = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref), + .addr_family = addr_family, + .ifindex = ifindex, + }; + + do { + nle = nl_recvmsgs(sk, + &((const struct nl_cb) { + .valid_cb = _ip_nexthop_dump_parse_cb, + .valid_arg = &data, + })); + } while (nle == -EAGAIN); + + nl_socket_free(sk); + + if (nle < 0) { + _LOGD("nexthop-dump: recv failed: %s (%d)", nm_strerror(nle), nle); + g_ptr_array_unref(data.result); + return NULL; + } + + return data.result; +nla_put_failure: + g_return_val_if_reached(NULL); +} + +typedef struct { + NMPObject *obj; +} NexthopGetData; + +static int +_ip_nexthop_get_parse_cb(const struct nl_msg *msg, void *arg) +{ + NexthopGetData *data = arg; + + nm_assert(!data->obj); + + data->obj = _new_from_nl_nexthop(nlmsg_hdr(msg), FALSE, FALSE); + if (!data->obj) + return NL_SKIP; + + return NL_OK; +} + +static gboolean +ip_nexthop_get(NMPlatform *platform, guint32 nh_id, NMPObject **out_obj) +{ + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nl_sock *sk = NULL; + struct nhmsg nhm = { + .nh_family = AF_UNSPEC, + }; + NexthopGetData data = { + .obj = NULL, + }; + int nle; + + nlmsg = nlmsg_alloc_new(0, RTM_GETNEXTHOP, 0); + if (!nlmsg) + g_return_val_if_reached(FALSE); + + if (nlmsg_append_struct(nlmsg, &nhm) < 0) + g_return_val_if_reached(FALSE); + + NLA_PUT_U32(nlmsg, NHA_ID, nh_id); + + nle = nl_socket_new(&sk, NETLINK_ROUTE, NL_SOCKET_FLAGS_DISABLE_MSG_PEEK, 0, 0); + if (nle < 0) { + _LOGD("nexthop-get: error opening socket: %s (%d)", nm_strerror(nle), nle); + return FALSE; + } + + nle = nl_send_auto(sk, nlmsg); + if (nle < 0) { + _LOGD("nexthop-get: failed sending request: %s (%d)", nm_strerror(nle), nle); + nl_socket_free(sk); + return FALSE; + } + + nle = nl_recvmsgs(sk, + &((const struct nl_cb) { + .valid_cb = _ip_nexthop_get_parse_cb, + .valid_arg = &data, + })); + + nl_socket_free(sk); + + if (nle < 0) { + nm_clear_pointer(&data.obj, nmp_object_unref); + return FALSE; + } + + if (!data.obj) + return FALSE; + + NM_SET_OUT(out_obj, data.obj); + return TRUE; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + static int ip_route_add(NMPlatform *platform, NMPNlmFlags flags, NMPObject *obj_stack, char **out_extack_msg) { @@ -10838,6 +11143,10 @@ object_delete(NMPlatform *platform, const NMPObject *obj) case NMP_OBJECT_TYPE_IP6_ROUTE: nlmsg = _nl_msg_new_route(RTM_DELROUTE, 0, obj); break; + case NMP_OBJECT_TYPE_IP4_NEXTHOP: + case NMP_OBJECT_TYPE_IP6_NEXTHOP: + nlmsg = _nl_msg_new_nexthop(RTM_DELNEXTHOP, 0, obj); + break; case NMP_OBJECT_TYPE_ROUTING_RULE: nlmsg = _nl_msg_new_routing_rule(RTM_DELRULE, 0, NMP_OBJECT_CAST_ROUTING_RULE(obj)); break; @@ -12524,6 +12833,10 @@ nm_linux_platform_class_init(NMLinuxPlatformClass *klass) platform_class->ip_route_add = ip_route_add; platform_class->ip_route_get = ip_route_get; + platform_class->ip_nexthop_add = ip_nexthop_add; + platform_class->ip_nexthop_dump = ip_nexthop_dump; + platform_class->ip_nexthop_get = ip_nexthop_get; + platform_class->routing_rule_add = routing_rule_add; platform_class->qdisc_add = qdisc_add; diff --git a/src/libnm-platform/nm-netlink.c b/src/libnm-platform/nm-netlink.c index 9eb6721f0e..be0c58043b 100644 --- a/src/libnm-platform/nm-netlink.c +++ b/src/libnm-platform/nm-netlink.c @@ -94,6 +94,9 @@ static NM_UTILS_LOOKUP_STR_DEFINE(_rtnl_type_to_str, NM_UTILS_LOOKUP_STR_ITEM(RTM_GETRULE, "RTM_GETRULE"), NM_UTILS_LOOKUP_STR_ITEM(RTM_NEWRULE, "RTM_NEWRULE"), NM_UTILS_LOOKUP_STR_ITEM(RTM_DELRULE, "RTM_DELRULE"), + NM_UTILS_LOOKUP_STR_ITEM(RTM_GETNEXTHOP, "RTM_GETNEXTHOP"), + NM_UTILS_LOOKUP_STR_ITEM(RTM_NEWNEXTHOP, "RTM_NEWNEXTHOP"), + NM_UTILS_LOOKUP_STR_ITEM(RTM_DELNEXTHOP, "RTM_DELNEXTHOP"), NM_UTILS_LOOKUP_STR_ITEM(RTM_GETQDISC, "RTM_GETQDISC"), NM_UTILS_LOOKUP_STR_ITEM(RTM_NEWQDISC, "RTM_NEWQDISC"), NM_UTILS_LOOKUP_STR_ITEM(RTM_DELQDISC, "RTM_DELQDISC"), diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 1f98a9a9c3..0e28366a6b 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -4782,6 +4782,84 @@ next_plat:; return success; } +gboolean +nm_platform_ip_nexthop_sync(NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray *known_nexthops, + GPtrArray *nexthops_prune, + GPtrArray *nexthops_platform) +{ + gs_unref_hashtable GHashTable *known_nexthops_idx = NULL; + gs_unref_hashtable GHashTable *nexthops_platform_idx = NULL; + int IS_IPv4 = NM_IS_IPv4(addr_family); + guint i; + gboolean success = TRUE; + + if (known_nexthops && known_nexthops->len > 0) { + known_nexthops_idx = g_hash_table_new(nm_direct_hash, NULL); + for (i = 0; i < known_nexthops->len; i++) { + const NMPObject *nh = known_nexthops->pdata[i]; + + g_hash_table_add(known_nexthops_idx, GUINT_TO_POINTER(nh->ipx_nexthop.nhx.id)); + } + } + + if (nexthops_platform && nexthops_platform->len > 0) { + nexthops_platform_idx = g_hash_table_new(nm_direct_hash, NULL); + for (i = 0; i < nexthops_platform->len; i++) { + const NMPObject *nh = nexthops_platform->pdata[i]; + + g_hash_table_insert(nexthops_platform_idx, + GUINT_TO_POINTER(nh->ipx_nexthop.nhx.id), + (gpointer) nh); + } + } + + if (nexthops_prune) { + for (i = 0; i < nexthops_prune->len; i++) { + const NMPObject *prune_o = nexthops_prune->pdata[i]; + + nm_assert((IS_IPv4 && NMP_OBJECT_GET_TYPE(prune_o) == NMP_OBJECT_TYPE_IP4_NEXTHOP) + || (!IS_IPv4 && NMP_OBJECT_GET_TYPE(prune_o) == NMP_OBJECT_TYPE_IP6_NEXTHOP)); + + if (nm_g_hash_table_lookup(known_nexthops_idx, + GUINT_TO_POINTER(NMP_OBJECT_CAST_IP_NEXTHOP(prune_o)->id))) + continue; + + if (!nm_platform_object_delete(self, prune_o)) { + /* ignore error... */ + } + } + } + + if (known_nexthops) { + for (i = 0; i < known_nexthops->len; i++) { + const NMPObject *nh = known_nexthops->pdata[i]; + const NMPObject *candidate; + + candidate = + nm_g_hash_table_lookup(nexthops_platform_idx, GUINT_TO_POINTER(nh->ip_nexthop.id)); + if (candidate + && (IS_IPv4 + ? nm_platform_ip4_nexthop_cmp(&nh->ip4_nexthop, + &candidate->ip4_nexthop, + NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_SEMANTICALLY) + : nm_platform_ip6_nexthop_cmp(&nh->ip6_nexthop, + &candidate->ip6_nexthop, + NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_SEMANTICALLY)) + == 0) { + continue; + } + + if (nm_platform_ip_nexthop_add(self, NMP_NLM_FLAG_REPLACE, nh, NULL) != 0) + success = FALSE; + } + } + + return success; +} + gboolean nm_platform_ip_address_flush(NMPlatform *self, int addr_family, int ifindex) { @@ -5376,6 +5454,69 @@ nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex) return success; } +GPtrArray * +nm_platform_ip_nexthop_dump(NMPlatform *self, int addr_family, int ifindex) +{ + _CHECK_SELF(self, klass, NULL); + + nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + return klass->ip_nexthop_dump(self, addr_family, ifindex); +} + +gboolean +nm_platform_ip_nexthop_get(NMPlatform *self, guint32 nh_id, NMPObject **out_obj) +{ + nm_auto_nmpobj NMPObject *obj = NULL; + gboolean result; + + _CHECK_SELF(self, klass, FALSE); + + nm_assert(nh_id > 0); + + if (!klass->ip_nexthop_get) { + NM_SET_OUT(out_obj, NULL); + return FALSE; + } + + result = klass->ip_nexthop_get(self, nh_id, &obj); + + if (result) { + nm_assert(obj); + _LOGD("nexthop: get nexthop %u succeeded", nh_id); + NM_SET_OUT(out_obj, g_steal_pointer(&obj)); + } else { + _LOGD("nexthop: get nexthop %u failed", nh_id); + NM_SET_OUT(out_obj, NULL); + } + + return result; +} + +gboolean +nm_platform_ip_nexthop_flush(NMPlatform *self, int addr_family, int ifindex) +{ + gboolean success = TRUE; + + _CHECK_SELF(self, klass, FALSE); + + nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) { + gs_unref_ptrarray GPtrArray *nexthops_prune = NULL; + + nexthops_prune = nm_platform_ip_nexthop_dump(self, AF_INET, ifindex); + success &= nm_platform_ip_nexthop_sync(self, AF_INET, ifindex, NULL, nexthops_prune, NULL); + } + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) { + gs_unref_ptrarray GPtrArray *nexthops_prune = NULL; + + nexthops_prune = nm_platform_ip_nexthop_dump(self, AF_INET6, ifindex); + success &= nm_platform_ip_nexthop_sync(self, AF_INET6, ifindex, NULL, nexthops_prune, NULL); + } + return success; +} + /*****************************************************************************/ static guint8 @@ -5629,6 +5770,34 @@ nm_platform_ip6_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformI return _ip_route_add(self, flags, &obj, NULL); } +int +nm_platform_ip_nexthop_add(NMPlatform *self, + NMPNlmFlags flags, + const NMPObject *obj, + char **out_extack_msg) +{ + NMPObject obj_stack; + char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; + int ifindex; + + _CHECK_SELF(self, klass, -NME_BUG); + + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), + NMP_OBJECT_TYPE_IP4_NEXTHOP, + NMP_OBJECT_TYPE_IP6_NEXTHOP)); + + nmp_object_stackinit(&obj_stack, NMP_OBJECT_GET_TYPE(obj), &obj->ip_nexthop); + + ifindex = obj_stack.ip_nexthop.ifindex; + + _LOG3D("nexthop: %-10s IPv%c nexthop: %s", + _nmp_nlm_flag_to_string(flags & NMP_NLM_FLAG_FMASK), + nm_utils_addr_family_to_char(NMP_OBJECT_GET_ADDR_FAMILY(&obj_stack)), + nmp_object_to_string(&obj_stack, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + + return klass->ip_nexthop_add(self, flags, &obj_stack, out_extack_msg); +} + gboolean nm_platform_object_delete(NMPlatform *self, const NMPObject *obj) { @@ -5647,6 +5816,8 @@ nm_platform_object_delete(NMPlatform *self, const NMPObject *obj) break; case NMP_OBJECT_TYPE_IP4_ROUTE: case NMP_OBJECT_TYPE_IP6_ROUTE: + case NMP_OBJECT_TYPE_IP4_NEXTHOP: + case NMP_OBJECT_TYPE_IP6_NEXTHOP: case NMP_OBJECT_TYPE_QDISC: case NMP_OBJECT_TYPE_TFILTER: ifindex = NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj)->ifindex; @@ -7438,6 +7609,52 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route, return buf0; } +const char * +nm_platform_ip4_nexthop_to_string(const NMPlatformIP4NextHop *nexthop, char *buf, gsize len) +{ + char s_gateway[INET_ADDRSTRLEN]; + char s_source[50]; + + if (!nm_utils_to_string_buffer_init_null(nexthop, &buf, &len)) + return buf; + + inet_ntop(AF_INET, &nexthop->gateway, s_gateway, sizeof(s_gateway)); + nmp_utils_ip_config_source_to_string(nexthop->nh_source, s_source, sizeof(s_source)); + + g_snprintf(buf, + len, + "id %u dev %d gw %s rt-src %s", + nexthop->id, + nexthop->ifindex, + s_gateway, + s_source); + + return buf; +} + +const char * +nm_platform_ip6_nexthop_to_string(const NMPlatformIP6NextHop *nexthop, char *buf, gsize len) +{ + char s_gateway[INET6_ADDRSTRLEN]; + char s_source[50]; + + if (!nm_utils_to_string_buffer_init_null(nexthop, &buf, &len)) + return buf; + + inet_ntop(AF_INET6, &nexthop->gateway, s_gateway, sizeof(s_gateway)); + nmp_utils_ip_config_source_to_string(nexthop->nh_source, s_source, sizeof(s_source)); + + g_snprintf(buf, + len, + "id %u dev %d gw %s rt-src %s", + nexthop->id, + nexthop->ifindex, + s_gateway, + s_source); + + return buf; +} + /** * nm_platform_ip6_route_to_string: * @route: pointer to NMPlatformIP6Route route structure @@ -7453,33 +7670,37 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route, 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)); @@ -7523,7 +7744,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 @@ -9321,23 +9542,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, @@ -9366,10 +9589,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, @@ -9419,9 +9643,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: @@ -9433,7 +9660,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 @@ -9441,7 +9667,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 1cdd160114..0dafabf93c 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 { @@ -1349,6 +1365,15 @@ typedef struct { int oif_ifindex, NMPObject **out_route); + int (*ip_nexthop_add)(NMPlatform *self, + NMPNlmFlags flags, + NMPObject *obj_stack, + char **out_extack_msg); + + GPtrArray *(*ip_nexthop_dump)(NMPlatform *self, int addr_family, int ifindex); + + gboolean (*ip_nexthop_get)(NMPlatform *self, guint32 nh_id, NMPObject **out_obj); + int (*routing_rule_add)(NMPlatform *self, NMPNlmFlags flags, const NMPlatformRoutingRule *routing_rule); @@ -2465,6 +2490,11 @@ int nm_platform_ip4_route_add(NMPlatform *self, const NMPlatformIP4RtNextHop *extra_nexthops); int nm_platform_ip6_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route); +int nm_platform_ip_nexthop_add(NMPlatform *self, + NMPNlmFlags flags, + const NMPObject *nexthop, + char **out_extack_msg); + GPtrArray *nm_platform_ip_route_get_prune_list(NMPlatform *self, int addr_family, int ifindex, @@ -2479,6 +2509,7 @@ gboolean nm_platform_ip_route_sync(NMPlatform *self, GPtrArray **out_routes_failed); gboolean nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex); +gboolean nm_platform_ip_nexthop_flush(NMPlatform *self, int addr_family, int ifindex); int nm_platform_ip_route_get(NMPlatform *self, int addr_family, @@ -2487,6 +2518,17 @@ int nm_platform_ip_route_get(NMPlatform *self, int oif_ifindex, NMPObject **out_route); +GPtrArray *nm_platform_ip_nexthop_dump(NMPlatform *self, int addr_family, int ifindex); + +gboolean nm_platform_ip_nexthop_get(NMPlatform *self, guint32 nh_id, NMPObject **out_obj); + +gboolean nm_platform_ip_nexthop_sync(NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray *known_nexthops, + GPtrArray *nexthops_prune, + GPtrArray *nexthops_platform); + int nm_platform_routing_rule_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformRoutingRule *routing_rule); @@ -2536,6 +2578,11 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz } const char *nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsize len); +const char * +nm_platform_ip4_nexthop_to_string(const NMPlatformIP4NextHop *nexthop, char *buf, gsize len); +const char * +nm_platform_ip6_nexthop_to_string(const NMPlatformIP6NextHop *nexthop, char *buf, gsize len); + const char * nm_platform_routing_rule_to_string(const NMPlatformRoutingRule *routing_rule, char *buf, gsize len); const char *nm_platform_qdisc_to_string(const NMPlatformQdisc *qdisc, char *buf, gsize len); diff --git a/src/libnm-platform/nmp-base.h b/src/libnm-platform/nmp-base.h index ae538f5bad..67aeab6a37 100644 --- a/src/libnm-platform/nmp-base.h +++ b/src/libnm-platform/nmp-base.h @@ -142,6 +142,8 @@ typedef struct _NMPlatformIP4Address NMPlatformIP4Address; typedef struct _NMPlatformIP4Route NMPlatformIP4Route; typedef struct _NMPlatformIP6Address NMPlatformIP6Address; typedef struct _NMPlatformIP6Route NMPlatformIP6Route; +typedef struct _NMPlatformIP4NextHop NMPlatformIP4NextHop; +typedef struct _NMPlatformIP6NextHop NMPlatformIP6NextHop; typedef struct _NMPlatformLink NMPlatformLink; typedef struct _NMPObject NMPObject; @@ -159,6 +161,11 @@ typedef enum _nm_packed { NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE, +#define NMP_OBJECT_TYPE_IP_NEXTHOP(is_ipv4) \ + ((is_ipv4) ? NMP_OBJECT_TYPE_IP4_NEXTHOP : NMP_OBJECT_TYPE_IP6_NEXTHOP) + NMP_OBJECT_TYPE_IP4_NEXTHOP, + NMP_OBJECT_TYPE_IP6_NEXTHOP, + NMP_OBJECT_TYPE_ROUTING_RULE, NMP_OBJECT_TYPE_QDISC, diff --git a/src/libnm-platform/nmp-object.c b/src/libnm-platform/nmp-object.c index d73eb641f1..07c1dabb30 100644 --- a/src/libnm-platform/nmp-object.c +++ b/src/libnm-platform/nmp-object.c @@ -381,6 +381,8 @@ _idx_obj_part(const DedupMultiIdxType *idx_type, NMP_OBJECT_TYPE_IP6_ADDRESS, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_IP4_NEXTHOP, + NMP_OBJECT_TYPE_IP6_NEXTHOP, NMP_OBJECT_TYPE_QDISC, NMP_OBJECT_TYPE_TFILTER, NMP_OBJECT_TYPE_MPTCP_ADDR) @@ -899,6 +901,22 @@ nmp_object_stackinit_id_ip6_address(NMPObject *obj, int ifindex, const struct in return obj; } +const NMPObject * +nmp_object_stackinit_id_ip4_nexthop(NMPObject *obj, guint32 id) +{ + _nmp_object_stackinit_from_type(obj, NMP_OBJECT_TYPE_IP4_NEXTHOP); + obj->ip4_nexthop.id = id; + return obj; +} + +const NMPObject * +nmp_object_stackinit_id_ip6_nexthop(NMPObject *obj, guint32 id) +{ + _nmp_object_stackinit_from_type(obj, NMP_OBJECT_TYPE_IP6_NEXTHOP); + obj->ip6_nexthop.id = id; + return obj; +} + /*****************************************************************************/ const char * @@ -1668,6 +1686,22 @@ _vt_cmd_plobj_id_cmp_ip6_address(const NMPlatformObject *obj1, const NMPlatformO NM_PLATFORM_IP_ADDRESS_CMP_TYPE_ID); } +static int +_vt_cmd_plobj_id_cmp_ip4_nexthop(const NMPlatformObject *obj1, const NMPlatformObject *obj2) +{ + return nm_platform_ip4_nexthop_cmp((const NMPlatformIP4NextHop *) obj1, + (const NMPlatformIP4NextHop *) obj2, + NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_ID); +} + +static int +_vt_cmd_plobj_id_cmp_ip6_nexthop(const NMPlatformObject *obj1, const NMPlatformObject *obj2) +{ + return nm_platform_ip6_nexthop_cmp((const NMPlatformIP6NextHop *) obj1, + (const NMPlatformIP6NextHop *) obj2, + NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_ID); +} + _vt_cmd_plobj_id_cmp(qdisc, NMPlatformQdisc, { NM_CMP_FIELD(obj1, obj2, ifindex); NM_CMP_FIELD(obj1, obj2, parent); @@ -1770,6 +1804,14 @@ _vt_cmd_plobj_id_hash_update(ip6_route, NMPlatformIP6Route, { nm_platform_ip6_route_hash_update(obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h); }); +_vt_cmd_plobj_id_hash_update(ip4_nexthop, NMPlatformIP4NextHop, { + nm_platform_ip4_nexthop_hash_update(obj, NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_ID, h); +}); + +_vt_cmd_plobj_id_hash_update(ip6_nexthop, NMPlatformIP6NextHop, { + nm_platform_ip6_nexthop_hash_update(obj, NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_ID, h); +}); + _vt_cmd_plobj_id_hash_update(routing_rule, NMPlatformRoutingRule, { nm_platform_routing_rule_hash_update(obj, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, h); }); @@ -1804,6 +1846,38 @@ _vt_cmd_plobj_cmp_ip6_route(const NMPlatformObject *obj1, const NMPlatformObject NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL); } +static int +_vt_cmd_plobj_cmp_ip4_nexthop(const NMPlatformObject *obj1, const NMPlatformObject *obj2) +{ + return nm_platform_ip4_nexthop_cmp((const NMPlatformIP4NextHop *) obj1, + (const NMPlatformIP4NextHop *) obj2, + NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_FULL); +} + +static void +_vt_cmd_plobj_hash_update_ip4_nexthop(const NMPlatformObject *obj, NMHashState *h) +{ + return nm_platform_ip4_nexthop_hash_update((const NMPlatformIP4NextHop *) obj, + NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_FULL, + h); +} + +static int +_vt_cmd_plobj_cmp_ip6_nexthop(const NMPlatformObject *obj1, const NMPlatformObject *obj2) +{ + return nm_platform_ip6_nexthop_cmp((const NMPlatformIP6NextHop *) obj1, + (const NMPlatformIP6NextHop *) obj2, + NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_FULL); +} + +static void +_vt_cmd_plobj_hash_update_ip6_nexthop(const NMPlatformObject *obj, NMHashState *h) +{ + return nm_platform_ip6_nexthop_hash_update((const NMPlatformIP6NextHop *) obj, + NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_FULL, + h); +} + static void _vt_cmd_plobj_hash_update_routing_rule(const NMPlatformObject *obj, NMHashState *h) { @@ -2263,6 +2337,8 @@ nmp_lookup_init_obj_type(NMPLookup *lookup, NMPObjectType obj_type) case NMP_OBJECT_TYPE_IP6_ADDRESS: case NMP_OBJECT_TYPE_IP4_ROUTE: case NMP_OBJECT_TYPE_IP6_ROUTE: + case NMP_OBJECT_TYPE_IP4_NEXTHOP: + case NMP_OBJECT_TYPE_IP6_NEXTHOP: case NMP_OBJECT_TYPE_ROUTING_RULE: case NMP_OBJECT_TYPE_QDISC: case NMP_OBJECT_TYPE_TFILTER: @@ -2300,6 +2376,8 @@ nmp_lookup_init_object_by_ifindex(NMPLookup *lookup, NMPObjectType obj_type, int NMP_OBJECT_TYPE_IP6_ADDRESS, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_IP4_NEXTHOP, + NMP_OBJECT_TYPE_IP6_NEXTHOP, NMP_OBJECT_TYPE_QDISC, NMP_OBJECT_TYPE_TFILTER, NMP_OBJECT_TYPE_MPTCP_ADDR)); @@ -3400,6 +3478,36 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = { .cmd_plobj_hash_update = _vt_cmd_plobj_hash_update_ip6_route, .cmd_plobj_cmp = _vt_cmd_plobj_cmp_ip6_route, }, + [NMP_OBJECT_TYPE_IP4_NEXTHOP - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_IP4_NEXTHOP, + .sizeof_data = sizeof(NMPObjectIP4NextHop), + .sizeof_public = sizeof(NMPlatformIP4NextHop), + .obj_type_name = "ip4-nexthop", + .addr_family = AF_INET, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip4_nexthop, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip4_nexthop, + .cmd_plobj_to_string_id = (CmdPlobjToStringIdFunc) nm_platform_ip4_nexthop_to_string, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_ip4_nexthop_to_string, + .cmd_plobj_hash_update = _vt_cmd_plobj_hash_update_ip4_nexthop, + .cmd_plobj_cmp = _vt_cmd_plobj_cmp_ip4_nexthop, + }, + [NMP_OBJECT_TYPE_IP6_NEXTHOP - 1] = + { + .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), + .obj_type = NMP_OBJECT_TYPE_IP6_NEXTHOP, + .sizeof_data = sizeof(NMPObjectIP6NextHop), + .sizeof_public = sizeof(NMPlatformIP6NextHop), + .obj_type_name = "ip6-nexthop", + .addr_family = AF_INET6, + .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip6_nexthop, + .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip6_nexthop, + .cmd_plobj_to_string_id = (CmdPlobjToStringIdFunc) nm_platform_ip6_nexthop_to_string, + .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_ip6_nexthop_to_string, + .cmd_plobj_hash_update = _vt_cmd_plobj_hash_update_ip6_nexthop, + .cmd_plobj_cmp = _vt_cmd_plobj_cmp_ip6_nexthop, + }, [NMP_OBJECT_TYPE_ROUTING_RULE - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), diff --git a/src/libnm-platform/nmp-object.h b/src/libnm-platform/nmp-object.h index a960c528c7..47964ec105 100644 --- a/src/libnm-platform/nmp-object.h +++ b/src/libnm-platform/nmp-object.h @@ -352,6 +352,14 @@ typedef struct { NMPlatformIP6Route _public; } NMPObjectIP6Route; +typedef struct { + NMPlatformIP4NextHop _public; +} NMPObjectIP4NextHop; + +typedef struct { + NMPlatformIP6NextHop _public; +} NMPObjectIP6NextHop; + typedef struct { NMPlatformRoutingRule _public; } NMPObjectRoutingRule; @@ -452,6 +460,13 @@ struct _NMPObject { NMPObjectIP4Route _ip4_route; NMPObjectIP6Route _ip6_route; + NMPlatformIPNextHop ip_nexthop; + NMPlatformIPXNextHop ipx_nexthop; + NMPlatformIP4NextHop ip4_nexthop; + NMPlatformIP6NextHop ip6_nexthop; + NMPObjectIP4NextHop _ip4_nexthop; + NMPObjectIP6NextHop _ip6_nexthop; + NMPlatformRoutingRule routing_rule; NMPObjectRoutingRule _routing_rule; @@ -543,6 +558,8 @@ _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX(NMPObjectType obj_type) case NMP_OBJECT_TYPE_IP6_ADDRESS: case NMP_OBJECT_TYPE_IP4_ROUTE: case NMP_OBJECT_TYPE_IP6_ROUTE: + case NMP_OBJECT_TYPE_IP4_NEXTHOP: + case NMP_OBJECT_TYPE_IP6_NEXTHOP: case NMP_OBJECT_TYPE_QDISC: @@ -628,6 +645,12 @@ _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX(NMPObjectType obj_type) _NMP_OBJECT_CAST(obj, ipx_route, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE) #define NMP_OBJECT_CAST_IP4_ROUTE(obj) _NMP_OBJECT_CAST(obj, ip4_route, NMP_OBJECT_TYPE_IP4_ROUTE) #define NMP_OBJECT_CAST_IP6_ROUTE(obj) _NMP_OBJECT_CAST(obj, ip6_route, NMP_OBJECT_TYPE_IP6_ROUTE) +#define NMP_OBJECT_CAST_IP4_NEXTHOP(obj) \ + _NMP_OBJECT_CAST(obj, ip4_nexthop, NMP_OBJECT_TYPE_IP4_NEXTHOP) +#define NMP_OBJECT_CAST_IP6_NEXTHOP(obj) \ + _NMP_OBJECT_CAST(obj, ip6_nexthop, NMP_OBJECT_TYPE_IP6_NEXTHOP) +#define NMP_OBJECT_CAST_IP_NEXTHOP(obj) \ + _NMP_OBJECT_CAST(obj, ip_nexthop, NMP_OBJECT_TYPE_IP4_NEXTHOP, NMP_OBJECT_TYPE_IP6_NEXTHOP) #define NMP_OBJECT_CAST_ROUTING_RULE(obj) \ _NMP_OBJECT_CAST(obj, routing_rule, NMP_OBJECT_TYPE_ROUTING_RULE) #define NMP_OBJECT_CAST_QDISC(obj) _NMP_OBJECT_CAST(obj, qdisc, NMP_OBJECT_TYPE_QDISC) @@ -767,6 +790,9 @@ const NMPObject *nmp_object_stackinit_id_ip4_address(NMPObject *obj, const NMPObject * nmp_object_stackinit_id_ip6_address(NMPObject *obj, int ifindex, const struct in6_addr *address); +const NMPObject *nmp_object_stackinit_id_ip4_nexthop(NMPObject *obj, guint32 id); +const NMPObject *nmp_object_stackinit_id_ip6_nexthop(NMPObject *obj, guint32 id); + const char *nmp_object_to_string(const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, diff --git a/src/libnm-platform/nmp-plobj.c b/src/libnm-platform/nmp-plobj.c index 70664634e6..c060ff90a0 100644 --- a/src/libnm-platform/nmp-plobj.c +++ b/src/libnm-platform/nmp-plobj.c @@ -170,6 +170,106 @@ nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, /*****************************************************************************/ +void +nm_platform_ip4_nexthop_hash_update(const NMPlatformIP4NextHop *obj, + NMPlatformIPNextHopCmpType cmp_type, + NMHashState *h) +{ + switch (cmp_type) { + case NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_ID: + nm_hash_update_vals(h, obj->id); + break; + case NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_SEMANTICALLY: + nm_hash_update_vals(h, + obj->id, + obj->ifindex, + nmp_utils_ip_config_source_round_trip_rtprot(obj->nh_source), + obj->gateway); + break; + case NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_FULL: + nm_hash_update_vals(h, obj->id, obj->ifindex, obj->nh_source, obj->gateway); + break; + } +} + +int +nm_platform_ip4_nexthop_cmp(const NMPlatformIP4NextHop *a, + const NMPlatformIP4NextHop *b, + NMPlatformIPNextHopCmpType cmp_type) +{ + NM_CMP_SELF(a, b); + + NM_CMP_FIELD(a, b, id); + + switch (cmp_type) { + case NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_ID: + return 0; + case NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_SEMANTICALLY: + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->nh_source), + nmp_utils_ip_config_source_round_trip_rtprot(b->nh_source)); + NM_CMP_FIELD(a, b, gateway); + return 0; + case NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_FULL: + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, nh_source); + NM_CMP_FIELD(a, b, gateway); + return 0; + } + return nm_assert_unreachable_val(0); +} + +void +nm_platform_ip6_nexthop_hash_update(const NMPlatformIP6NextHop *obj, + NMPlatformIPNextHopCmpType cmp_type, + NMHashState *h) +{ + switch (cmp_type) { + case NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_ID: + nm_hash_update_vals(h, obj->id); + break; + case NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_SEMANTICALLY: + nm_hash_update_vals(h, + obj->id, + obj->ifindex, + nmp_utils_ip_config_source_round_trip_rtprot(obj->nh_source), + obj->gateway); + break; + case NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_FULL: + nm_hash_update_vals(h, obj->id, obj->ifindex, obj->nh_source, obj->gateway); + break; + } +} + +int +nm_platform_ip6_nexthop_cmp(const NMPlatformIP6NextHop *a, + const NMPlatformIP6NextHop *b, + NMPlatformIPNextHopCmpType cmp_type) +{ + NM_CMP_SELF(a, b); + + NM_CMP_FIELD(a, b, id); + + switch (cmp_type) { + case NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_ID: + return 0; + case NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_SEMANTICALLY: + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->nh_source), + nmp_utils_ip_config_source_round_trip_rtprot(b->nh_source)); + NM_CMP_FIELD_IN6ADDR(a, b, gateway); + return 0; + case NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_FULL: + NM_CMP_FIELD(a, b, ifindex); + NM_CMP_FIELD(a, b, nh_source); + NM_CMP_FIELD_IN6ADDR(a, b, gateway); + return 0; + } + return nm_assert_unreachable_val(0); +} + +/*****************************************************************************/ + void nm_platform_ip6_address_hash_update(const NMPlatformIP6Address *obj, NMHashState *h) { diff --git a/src/libnm-platform/nmp-plobj.h b/src/libnm-platform/nmp-plobj.h index 7760c02c19..bb7a1d2f2f 100644 --- a/src/libnm-platform/nmp-plobj.h +++ b/src/libnm-platform/nmp-plobj.h @@ -141,6 +141,34 @@ typedef union { /*****************************************************************************/ +typedef struct { + __NMPlatformObjWithIfindex_COMMON; + guint32 id; + NMIPConfigSource nh_source; +} _nm_alignas(NMPlatformObject) NMPlatformIPNextHop; + +struct _NMPlatformIP4NextHop { + __NMPlatformObjWithIfindex_COMMON; + guint32 id; + NMIPConfigSource nh_source; + in_addr_t gateway; +} _nm_alignas(NMPlatformObject); + +struct _NMPlatformIP6NextHop { + __NMPlatformObjWithIfindex_COMMON; + guint32 id; + NMIPConfigSource nh_source; + struct in6_addr gateway; +} _nm_alignas(NMPlatformObject); + +typedef union { + NMPlatformIPNextHop nhx; + NMPlatformIP4NextHop nh4; + NMPlatformIP6NextHop nh6; +} NMPlatformIPXNextHop; + +/*****************************************************************************/ + typedef enum { NM_PLATFORM_IP_ADDRESS_CMP_TYPE_ID, @@ -162,6 +190,14 @@ typedef enum { /*****************************************************************************/ +typedef enum { + NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_ID, + NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_SEMANTICALLY, + NM_PLATFORM_IP_NEXTHOP_CMP_TYPE_FULL, +} NMPlatformIPNextHopCmpType; + +/*****************************************************************************/ + typedef struct { bool is_ip4; NMPObjectType obj_type; @@ -299,6 +335,22 @@ gboolean nm_platform_ip_address_match(int addr_family, const NMPlatformIPAddress *addr, NMPlatformMatchFlags match_flag); +void nm_platform_ip4_nexthop_hash_update(const NMPlatformIP4NextHop *obj, + NMPlatformIPNextHopCmpType cmp_type, + NMHashState *h); + +int nm_platform_ip4_nexthop_cmp(const NMPlatformIP4NextHop *a, + const NMPlatformIP4NextHop *b, + NMPlatformIPNextHopCmpType cmp_type); + +void nm_platform_ip6_nexthop_hash_update(const NMPlatformIP6NextHop *obj, + NMPlatformIPNextHopCmpType cmp_type, + NMHashState *h); + +int nm_platform_ip6_nexthop_cmp(const NMPlatformIP6NextHop *a, + const NMPlatformIP6NextHop *b, + NMPlatformIPNextHopCmpType cmp_type); + /*****************************************************************************/ #endif /* __NMP_PLOBJ_H__ */