/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2005 - 2017 Red Hat, Inc. * Copyright (C) 2006 - 2008 Novell, Inc. */ #include "src/core/nm-default-daemon.h" #include "nm-ip6-config.h" #include #include #include #include #include "nm-glib-aux/nm-dedup-multi.h" #include "nm-utils.h" #include "platform/nmp-object.h" #include "platform/nm-platform.h" #include "nm-platform/nm-platform-utils.h" #include "nm-core-internal.h" #include "NetworkManagerUtils.h" #include "nm-ip4-config.h" #include "ndisc/nm-ndisc.h" #include "nm-dbus-object.h" /*****************************************************************************/ static gboolean _route_valid(const NMPlatformIP6Route *r) { struct in6_addr n; return r && r->plen <= 128 && (memcmp(&r->network, nm_utils_ip6_address_clear_host_address(&n, &r->network, r->plen), sizeof(n)) == 0); } /*****************************************************************************/ typedef struct { int ifindex; int dns_priority; NMSettingIP6ConfigPrivacy privacy; GArray * nameservers; GPtrArray * domains; GPtrArray * searches; GPtrArray * dns_options; GVariant * address_data_variant; GVariant * addresses_variant; GVariant * route_data_variant; GVariant * routes_variant; NMDedupMultiIndex * multi_idx; const NMPObject * best_default_route; union { NMIPConfigDedupMultiIdxType idx_ip6_addresses_; NMDedupMultiIdxType idx_ip6_addresses; }; union { NMIPConfigDedupMultiIdxType idx_ip6_routes_; NMDedupMultiIdxType idx_ip6_routes; }; NMIPConfigFlags config_flags; bool never_default : 1; } NMIP6ConfigPrivate; struct _NMIP6Config { NMIPConfig parent; NMIP6ConfigPrivate _priv; }; struct _NMIP6ConfigClass { NMIPConfigClass parent; }; G_DEFINE_TYPE(NMIP6Config, nm_ip6_config, NM_TYPE_IP_CONFIG) #define NM_IP6_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMIP6Config, NM_IS_IP6_CONFIG) NM_GOBJECT_PROPERTIES_DEFINE(NMIP6Config, PROP_MULTI_IDX, PROP_IFINDEX, PROP_ADDRESS_DATA, PROP_ADDRESSES, PROP_ROUTE_DATA, PROP_ROUTES, PROP_GATEWAY, PROP_NAMESERVERS, PROP_DOMAINS, PROP_SEARCHES, PROP_DNS_OPTIONS, PROP_DNS_PRIORITY, ); /*****************************************************************************/ static void _add_address(NMIP6Config *self, const NMPObject *obj_new, const NMPlatformIP6Address *new); static void _add_route(NMIP6Config * self, const NMPObject *obj_new, const NMPlatformIP6Route *new, const NMPObject **out_obj_new); static const NMDedupMultiEntry * _lookup_route(const NMIP6Config *self, const NMPObject *needle, NMPlatformIPRouteCmpType cmp_type); /*****************************************************************************/ int nm_ip6_config_get_ifindex(const NMIP6Config *self) { return NM_IP6_CONFIG_GET_PRIVATE(self)->ifindex; } NMDedupMultiIndex * nm_ip6_config_get_multi_idx(const NMIP6Config *self) { return NM_IP6_CONFIG_GET_PRIVATE(self)->multi_idx; } void nm_ip6_config_set_privacy(NMIP6Config *self, NMSettingIP6ConfigPrivacy privacy) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); priv->privacy = privacy; } /*****************************************************************************/ void nm_ip6_config_set_never_default(NMIP6Config *self, gboolean never_default) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); priv->never_default = never_default; } gboolean nm_ip6_config_get_never_default(const NMIP6Config *self) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); return priv->never_default; } /*****************************************************************************/ const NMDedupMultiHeadEntry * nm_ip6_config_lookup_addresses(const NMIP6Config *self) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); return nm_dedup_multi_index_lookup_head(priv->multi_idx, &priv->idx_ip6_addresses, NULL); } void nm_ip_config_iter_ip6_address_init(NMDedupMultiIter *ipconf_iter, const NMIP6Config *self) { nm_assert(NM_IS_IP6_CONFIG(self)); nm_dedup_multi_iter_init(ipconf_iter, nm_ip6_config_lookup_addresses(self)); } /*****************************************************************************/ const NMDedupMultiHeadEntry * nm_ip6_config_lookup_routes(const NMIP6Config *self) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); return nm_dedup_multi_index_lookup_head(priv->multi_idx, &priv->idx_ip6_routes, NULL); } void nm_ip_config_iter_ip6_route_init(NMDedupMultiIter *ipconf_iter, const NMIP6Config *self) { nm_assert(NM_IS_IP6_CONFIG(self)); nm_dedup_multi_iter_init(ipconf_iter, nm_ip6_config_lookup_routes(self)); } /*****************************************************************************/ const NMPObject * nm_ip6_config_best_default_route_get(const NMIP6Config *self) { g_return_val_if_fail(NM_IS_IP6_CONFIG(self), NULL); return NM_IP6_CONFIG_GET_PRIVATE(self)->best_default_route; } const NMPObject * _nm_ip6_config_best_default_route_find(const NMIP6Config *self) { NMDedupMultiIter ipconf_iter; const NMPObject *new_best_default_route = NULL; nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, NULL) { new_best_default_route = _nm_ip_config_best_default_route_find_better(new_best_default_route, ipconf_iter.current->obj); } return new_best_default_route; } /*****************************************************************************/ static void _notify_addresses(NMIP6Config *self) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); nm_clear_g_variant(&priv->address_data_variant); nm_clear_g_variant(&priv->addresses_variant); nm_gobject_notify_together(self, PROP_ADDRESS_DATA, PROP_ADDRESSES); } static void _notify_routes(NMIP6Config *self) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); nm_assert(priv->best_default_route == _nm_ip6_config_best_default_route_find(self)); nm_clear_g_variant(&priv->route_data_variant); nm_clear_g_variant(&priv->routes_variant); nm_gobject_notify_together(self, PROP_ROUTE_DATA, PROP_ROUTES); } /*****************************************************************************/ static int sort_captured_addresses(const CList *lst_a, const CList *lst_b, gconstpointer user_data) { return nm_platform_ip6_address_pretty_sort_cmp( NMP_OBJECT_CAST_IP6_ADDRESS(c_list_entry(lst_a, NMDedupMultiEntry, lst_entries)->obj), NMP_OBJECT_CAST_IP6_ADDRESS(c_list_entry(lst_b, NMDedupMultiEntry, lst_entries)->obj), (((NMSettingIP6ConfigPrivacy) GPOINTER_TO_INT(user_data)) == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR)); } gboolean _nmtst_ip6_config_addresses_sort(NMIP6Config *self) { NMIP6ConfigPrivate * priv; const NMDedupMultiHeadEntry *head_entry; g_return_val_if_fail(NM_IS_IP6_CONFIG(self), FALSE); head_entry = nm_ip6_config_lookup_addresses(self); if (head_entry && head_entry->len > 1) { gboolean changed; gs_free gconstpointer *addresses_old = NULL; guint naddr, j; NMDedupMultiIter iter; priv = NM_IP6_CONFIG_GET_PRIVATE(self); addresses_old = nm_dedup_multi_objs_to_array_head(head_entry, NULL, NULL, &naddr); nm_assert(addresses_old); nm_assert(naddr > 0 && naddr == head_entry->len); nm_dedup_multi_head_entry_sort(head_entry, sort_captured_addresses, GINT_TO_POINTER(priv->privacy)); changed = FALSE; j = 0; nm_dedup_multi_iter_for_each (&iter, head_entry) { nm_assert(j < naddr); if (iter.current->obj != addresses_old[j++]) changed = TRUE; } nm_assert(j == naddr); if (changed) { _notify_addresses(self); return TRUE; } } return FALSE; } NMIP6Config * nm_ip6_config_clone(const NMIP6Config *self) { NMIP6Config *copy; copy = nm_ip6_config_new(nm_ip6_config_get_multi_idx(self), -1); nm_ip6_config_replace(copy, self, NULL); return copy; } NMIP6Config * nm_ip6_config_capture(NMDedupMultiIndex * multi_idx, NMPlatform * platform, int ifindex, NMSettingIP6ConfigPrivacy use_temporary) { NMIP6Config * self; NMIP6ConfigPrivate * priv; const NMDedupMultiHeadEntry *head_entry; NMDedupMultiIter iter; const NMPObject * plobj = NULL; nm_assert(ifindex > 0); /* Slaves have no IP configuration */ if (nm_platform_link_get_master(platform, ifindex) > 0) return NULL; self = nm_ip6_config_new(multi_idx, ifindex); priv = NM_IP6_CONFIG_GET_PRIVATE(self); head_entry = nm_platform_lookup_object(platform, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex); if (head_entry) { nmp_cache_iter_for_each (&iter, head_entry, &plobj) { if (!_nm_ip_config_add_obj(priv->multi_idx, &priv->idx_ip6_addresses_, ifindex, plobj, NULL, FALSE, TRUE, NULL, NULL)) nm_assert_not_reached(); } head_entry = nm_ip6_config_lookup_addresses(self); nm_assert(head_entry); nm_dedup_multi_head_entry_sort(head_entry, sort_captured_addresses, GINT_TO_POINTER(use_temporary)); _notify_addresses(self); } head_entry = nm_platform_lookup_object(platform, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex); nmp_cache_iter_for_each (&iter, head_entry, &plobj) _add_route(self, plobj, NULL, NULL); return self; } void nm_ip6_config_update_routes_metric(NMIP6Config *self, gint64 metric) { gs_free NMPlatformIP6Route *routes = NULL; gboolean need_update = FALSE; const NMPlatformIP6Route * r; NMDedupMultiIter iter; guint num = 0, i = 0; nm_ip_config_iter_ip6_route_for_each (&iter, self, &r) { if (r->metric != metric) need_update = TRUE; num++; } if (!need_update) return; routes = g_new(NMPlatformIP6Route, num); nm_ip_config_iter_ip6_route_for_each (&iter, self, &r) { routes[i] = *r; routes[i].metric = metric; i++; } g_object_freeze_notify(G_OBJECT(self)); nm_ip6_config_reset_routes(self); for (i = 0; i < num; i++) nm_ip6_config_add_route(self, &routes[i], NULL); g_object_thaw_notify(G_OBJECT(self)); } void nm_ip6_config_add_dependent_routes(NMIP6Config *self, guint32 route_table, guint32 route_metric, gboolean is_vrf) { const NMPlatformIP6Address *my_addr; const NMPlatformIP6Route * my_route; int ifindex; NMDedupMultiIter iter; g_return_if_fail(NM_IS_IP6_CONFIG(self)); ifindex = nm_ip6_config_get_ifindex(self); g_return_if_fail(ifindex > 0); /* For IPv6 addresses received via SLAAC/autoconf, we explicitly add the * device-routes (onlink) to NMIP6Config. * * For manually added IPv6 routes, add the device routes explicitly. */ /* Pre-generate multicast route */ { nm_auto_nmpobj NMPObject *r = NULL; NMPlatformIP6Route * route; r = nmp_object_new(NMP_OBJECT_TYPE_IP6_ROUTE, NULL); route = NMP_OBJECT_CAST_IP6_ROUTE(r); route->ifindex = ifindex; route->network.s6_addr[0] = 0xffu; route->plen = 8; route->table_coerced = nm_platform_route_table_coerce(RT_TABLE_LOCAL); route->type_coerced = nm_platform_route_type_coerce(RTN_UNICAST); route->metric = 256; _add_route(self, r, NULL, NULL); } nm_ip_config_iter_ip6_address_for_each (&iter, self, &my_addr) { NMPlatformIP6Route *route; gboolean has_peer; int routes_n, routes_i; if (my_addr->external) continue; { nm_auto_nmpobj NMPObject *r = NULL; /* Pre-generate local route added by kernel */ r = nmp_object_new(NMP_OBJECT_TYPE_IP6_ROUTE, NULL); route = NMP_OBJECT_CAST_IP6_ROUTE(r); route->ifindex = ifindex; route->network = my_addr->address; route->plen = 128; route->type_coerced = nm_platform_route_type_coerce(RTN_LOCAL); route->metric = 0; route->table_coerced = nm_platform_route_table_coerce(is_vrf ? route_table : RT_TABLE_LOCAL); _add_route(self, r, NULL, NULL); } if (NM_FLAGS_HAS(my_addr->n_ifa_flags, IFA_F_NOPREFIXROUTE)) continue; if (my_addr->plen == 0) continue; has_peer = !IN6_IS_ADDR_UNSPECIFIED(&my_addr->peer_address); /* If we have an IPv6 peer, we add two /128 routes * (unless, both addresses are identical). */ routes_n = (has_peer && !IN6_ARE_ADDR_EQUAL(&my_addr->address, &my_addr->peer_address)) ? 2 : 1; for (routes_i = 0; routes_i < routes_n; routes_i++) { nm_auto_nmpobj NMPObject *r = NULL; r = nmp_object_new(NMP_OBJECT_TYPE_IP6_ROUTE, NULL); route = NMP_OBJECT_CAST_IP6_ROUTE(r); route->ifindex = ifindex; route->rt_source = NM_IP_CONFIG_SOURCE_KERNEL; route->table_coerced = nm_platform_route_table_coerce(route_table); route->metric = route_metric; if (has_peer) { if (routes_i == 0) route->network = my_addr->address; else route->network = my_addr->peer_address; route->plen = 128; } else { nm_utils_ip6_address_clear_host_address(&route->network, &my_addr->address, my_addr->plen); route->plen = my_addr->plen; } nm_platform_ip_route_normalize(AF_INET6, (NMPlatformIPRoute *) route); if (_lookup_route(self, r, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID)) { /* we already track this route. Don't add it again. */ } else _add_route(self, r, NULL, NULL); } } again: nm_ip_config_iter_ip6_route_for_each (&iter, self, &my_route) { NMPlatformIP6Route rt; if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(my_route) || IN6_IS_ADDR_UNSPECIFIED(&my_route->gateway) || NM_IS_IP_CONFIG_SOURCE_RTPROT(my_route->rt_source) || nm_ip6_config_get_direct_route_for_host( self, &my_route->gateway, nm_platform_route_table_uncoerce(my_route->table_coerced, TRUE))) continue; rt = *my_route; rt.network = my_route->gateway; rt.plen = 128; rt.gateway = in6addr_any; _add_route(self, NULL, &rt, NULL); /* adding the route might have invalidated the iteration. Start again. */ goto again; } } gboolean nm_ip6_config_commit(const NMIP6Config * self, NMPlatform * platform, NMIPRouteTableSyncMode route_table_sync, GPtrArray ** out_temporary_not_available) { gs_unref_ptrarray GPtrArray *addresses = NULL; gs_unref_ptrarray GPtrArray *routes = NULL; gs_unref_ptrarray GPtrArray *routes_prune = NULL; int ifindex; gboolean success = TRUE; g_return_val_if_fail(NM_IS_IP6_CONFIG(self), FALSE); ifindex = nm_ip6_config_get_ifindex(self); g_return_val_if_fail(ifindex > 0, FALSE); addresses = nm_dedup_multi_objs_to_ptr_array_head(nm_ip6_config_lookup_addresses(self), NULL, NULL); routes = nm_dedup_multi_objs_to_ptr_array_head(nm_ip6_config_lookup_routes(self), NULL, NULL); routes_prune = nm_platform_ip_route_get_prune_list(platform, AF_INET6, ifindex, route_table_sync); nm_platform_ip6_address_sync(platform, ifindex, addresses, FALSE); if (!nm_platform_ip_route_sync(platform, AF_INET6, ifindex, routes, routes_prune, out_temporary_not_available)) success = FALSE; return success; } void nm_ip6_config_merge_setting(NMIP6Config * self, NMSettingIPConfig *setting, guint32 route_table, guint32 route_metric) { guint naddresses, nroutes, nnameservers, nsearches; const char * gateway_str; struct in6_addr gateway_bin; int i, priority; if (!setting) return; g_return_if_fail(NM_IS_SETTING_IP6_CONFIG(setting)); naddresses = nm_setting_ip_config_get_num_addresses(setting); nroutes = nm_setting_ip_config_get_num_routes(setting); nnameservers = nm_setting_ip_config_get_num_dns(setting); nsearches = nm_setting_ip_config_get_num_dns_searches(setting); g_object_freeze_notify(G_OBJECT(self)); /* Gateway */ if (!nm_setting_ip_config_get_never_default(setting) && (gateway_str = nm_setting_ip_config_get_gateway(setting)) && inet_pton(AF_INET6, gateway_str, &gateway_bin) == 1 && !IN6_IS_ADDR_UNSPECIFIED(&gateway_bin)) { const NMPlatformIP6Route r = { .rt_source = NM_IP_CONFIG_SOURCE_USER, .gateway = gateway_bin, .table_coerced = nm_platform_route_table_coerce(route_table), .metric = route_metric, }; _add_route(self, NULL, &r, NULL); } /* Addresses */ for (i = 0; i < naddresses; i++) { NMIPAddress * s_addr = nm_setting_ip_config_get_address(setting, i); NMPlatformIP6Address address; memset(&address, 0, sizeof(address)); nm_ip_address_get_address_binary(s_addr, &address.address); address.plen = nm_ip_address_get_prefix(s_addr); nm_assert(address.plen <= 128); address.lifetime = NM_PLATFORM_LIFETIME_PERMANENT; address.preferred = NM_PLATFORM_LIFETIME_PERMANENT; address.addr_source = NM_IP_CONFIG_SOURCE_USER; _add_address(self, NULL, &address); } /* Routes */ for (i = 0; i < nroutes; i++) { NMIPRoute * s_route = nm_setting_ip_config_get_route(setting, i); NMPlatformIP6Route route; gint64 m; if (nm_ip_route_get_family(s_route) != AF_INET6) { nm_assert_not_reached(); continue; } memset(&route, 0, sizeof(route)); nm_ip_route_get_dest_binary(s_route, &route.network); route.plen = nm_ip_route_get_prefix(s_route); nm_assert(route.plen <= 128); nm_ip_route_get_next_hop_binary(s_route, &route.gateway); m = nm_ip_route_get_metric(s_route); if (m < 0) route.metric = route_metric; else route.metric = nm_utils_ip6_route_metric_normalize(m); route.rt_source = NM_IP_CONFIG_SOURCE_USER; nm_utils_ip6_address_clear_host_address(&route.network, &route.network, route.plen); nm_utils_ip_route_attribute_to_platform(AF_INET6, s_route, NM_PLATFORM_IP_ROUTE_CAST(&route), route_table); _add_route(self, NULL, &route, NULL); } /* DNS */ if (nm_setting_ip_config_get_ignore_auto_dns(setting)) { nm_ip6_config_reset_nameservers(self); nm_ip6_config_reset_domains(self); nm_ip6_config_reset_searches(self); } for (i = 0; i < nnameservers; i++) { struct in6_addr ip; if (inet_pton(AF_INET6, nm_setting_ip_config_get_dns(setting, i), &ip) == 1) nm_ip6_config_add_nameserver(self, &ip); } for (i = 0; i < nsearches; i++) nm_ip6_config_add_search(self, nm_setting_ip_config_get_dns_search(setting, i)); i = 0; while ((i = nm_setting_ip_config_next_valid_dns_option(setting, i)) >= 0) { nm_ip6_config_add_dns_option(self, nm_setting_ip_config_get_dns_option(setting, i)); i++; } priority = nm_setting_ip_config_get_dns_priority(setting); if (priority) nm_ip6_config_set_dns_priority(self, priority); nm_ip6_config_set_never_default(self, nm_setting_ip_config_get_never_default(setting)); g_object_thaw_notify(G_OBJECT(self)); } NMSetting * nm_ip6_config_create_setting(const NMIP6Config *self, gboolean maybe_ipv6_disabled) { const NMIP6ConfigPrivate * priv; NMSettingIPConfig * s_ip6; guint nnameservers, nsearches, noptions; const char * method = NULL; char sbuf[NM_UTILS_INET_ADDRSTRLEN]; int i; NMDedupMultiIter ipconf_iter; const NMPlatformIP6Address *address; const NMPlatformIP6Route * route; s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new()); if (!self) { g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL); return NM_SETTING(s_ip6); } priv = NM_IP6_CONFIG_GET_PRIVATE(self); nnameservers = nm_ip6_config_get_num_nameservers(self); nsearches = nm_ip6_config_get_num_searches(self); noptions = nm_ip6_config_get_num_dns_options(self); /* Addresses */ nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, self, &address) { NMIPAddress *s_addr; /* Ignore link-local address. */ if (IN6_IS_ADDR_LINKLOCAL(&address->address)) { if (!method) method = NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL; continue; } /* Detect dynamic address */ if (address->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) { method = NM_SETTING_IP6_CONFIG_METHOD_AUTO; continue; } /* Static address found. */ if (!method || strcmp(method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL; s_addr = nm_ip_address_new_binary(AF_INET6, &address->address, address->plen, NULL); nm_setting_ip_config_add_address(s_ip6, s_addr); nm_ip_address_unref(s_addr); } /* Gateway */ if (priv->best_default_route && nm_setting_ip_config_get_num_addresses(s_ip6) > 0) { g_object_set( s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, _nm_utils_inet6_ntop(&NMP_OBJECT_CAST_IP6_ROUTE(priv->best_default_route)->gateway, sbuf), NULL); } /* Use 'ignore' if the method wasn't previously set */ if (!method) { method = maybe_ipv6_disabled ? NM_SETTING_IP6_CONFIG_METHOD_DISABLED : NM_SETTING_IP6_CONFIG_METHOD_IGNORE; } g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, method, NULL); /* Routes */ nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, &route) { NMIPRoute *s_route; /* Ignore link-local route. */ if (IN6_IS_ADDR_LINKLOCAL(&route->network)) continue; if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route)) continue; /* Ignore routes provided by external sources */ if (route->rt_source != nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER)) continue; s_route = nm_ip_route_new_binary(AF_INET6, &route->network, route->plen, &route->gateway, route->metric, NULL); nm_setting_ip_config_add_route(s_ip6, s_route); nm_ip_route_unref(s_route); } /* DNS */ for (i = 0; i < nnameservers; i++) { const struct in6_addr *nameserver = nm_ip6_config_get_nameserver(self, i); nm_setting_ip_config_add_dns(s_ip6, _nm_utils_inet6_ntop(nameserver, sbuf)); } for (i = 0; i < nsearches; i++) { const char *search = nm_ip6_config_get_search(self, i); nm_setting_ip_config_add_dns_search(s_ip6, search); } for (i = 0; i < noptions; i++) { const char *option = nm_ip6_config_get_dns_option(self, i); nm_setting_ip_config_add_dns_option(s_ip6, option); } g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DNS_PRIORITY, nm_ip6_config_get_dns_priority(self), NULL); return NM_SETTING(s_ip6); } /*****************************************************************************/ void nm_ip6_config_merge(NMIP6Config * dst, const NMIP6Config * src, NMIPConfigMergeFlags merge_flags, guint32 default_route_metric_penalty) { guint32 i; NMDedupMultiIter ipconf_iter; const NMPlatformIP6Address *address = NULL; const NMIP6ConfigPrivate * src_priv; g_return_if_fail(src != NULL); g_return_if_fail(dst != NULL); src_priv = NM_IP6_CONFIG_GET_PRIVATE(src); g_object_freeze_notify(G_OBJECT(dst)); /* addresses */ nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, src, &address) { if (NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_EXTERNAL) && !address->external) { NMPlatformIP6Address a; a = *address; a.external = TRUE; _add_address(dst, NULL, &a); } else _add_address(dst, NMP_OBJECT_UP_CAST(address), NULL); } /* nameservers */ if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { for (i = 0; i < nm_ip6_config_get_num_nameservers(src); i++) nm_ip6_config_add_nameserver(dst, nm_ip6_config_get_nameserver(src, i)); } /* routes */ if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_ROUTES)) { const NMPlatformIP6Route *r_src; nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, src, &r_src) { if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r_src)) { if (NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES) && !NM_FLAGS_HAS(src_priv->config_flags, NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES)) continue; if (default_route_metric_penalty) { NMPlatformIP6Route r = *r_src; r.metric = nm_utils_ip_route_metric_penalize(r.metric, default_route_metric_penalty); _add_route(dst, NULL, &r, NULL); continue; } } _add_route(dst, ipconf_iter.current->obj, NULL, NULL); } } /* domains */ if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { for (i = 0; i < nm_ip6_config_get_num_domains(src); i++) nm_ip6_config_add_domain(dst, nm_ip6_config_get_domain(src, i)); } /* dns searches */ if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { for (i = 0; i < nm_ip6_config_get_num_searches(src); i++) nm_ip6_config_add_search(dst, nm_ip6_config_get_search(src, i)); } /* dns options */ if (!NM_FLAGS_HAS(merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) { for (i = 0; i < nm_ip6_config_get_num_dns_options(src); i++) nm_ip6_config_add_dns_option(dst, nm_ip6_config_get_dns_option(src, i)); } /* DNS priority */ if (nm_ip6_config_get_dns_priority(src)) nm_ip6_config_set_dns_priority(dst, nm_ip6_config_get_dns_priority(src)); g_object_thaw_notify(G_OBJECT(dst)); } /*****************************************************************************/ static int _nameservers_get_index(const NMIP6Config *self, const struct in6_addr *ns) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); guint i; for (i = 0; i < priv->nameservers->len; i++) { const struct in6_addr *n = &g_array_index(priv->nameservers, struct in6_addr, i); if (IN6_ARE_ADDR_EQUAL(ns, n)) return (int) i; } return -1; } static int _domains_get_index(const NMIP6Config *self, const char *domain) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); guint i; for (i = 0; i < priv->domains->len; i++) { const char *d = g_ptr_array_index(priv->domains, i); if (g_strcmp0(domain, d) == 0) return (int) i; } return -1; } static int _searches_get_index(const NMIP6Config *self, const char *search) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); guint i; for (i = 0; i < priv->searches->len; i++) { const char *s = g_ptr_array_index(priv->searches, i); if (g_strcmp0(search, s) == 0) return (int) i; } return -1; } static int _dns_options_get_index(const NMIP6Config *self, const char *option) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); guint i; for (i = 0; i < priv->dns_options->len; i++) { const char *s = g_ptr_array_index(priv->dns_options, i); if (g_strcmp0(option, s) == 0) return (int) i; } return -1; } /*****************************************************************************/ /** * nm_ip6_config_subtract: * @dst: config from which to remove everything in @src * @src: config to remove from @dst * @default_route_metric_penalty: pretend that on source we applied * a route penalty on the default-route. It means, for default routes * we don't remove routes that match exactly, but those with a lower * metric (with the penalty removed). * * Removes everything in @src from @dst. */ void nm_ip6_config_subtract(NMIP6Config * dst, const NMIP6Config *src, guint32 default_route_metric_penalty) { NMIP6ConfigPrivate * dst_priv; guint i; int idx; const NMPlatformIP6Address *a; const NMPlatformIP6Route * r; NMDedupMultiIter ipconf_iter; gboolean changed; gboolean changed_default_route; g_return_if_fail(src != NULL); g_return_if_fail(dst != NULL); dst_priv = NM_IP6_CONFIG_GET_PRIVATE(dst); g_object_freeze_notify(G_OBJECT(dst)); /* addresses */ changed = FALSE; nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, src, &a) { if (nm_dedup_multi_index_remove_obj(dst_priv->multi_idx, &dst_priv->idx_ip6_addresses, NMP_OBJECT_UP_CAST(a), NULL)) changed = TRUE; } if (changed) _notify_addresses(dst); /* nameservers */ for (i = 0; i < nm_ip6_config_get_num_nameservers(src); i++) { idx = _nameservers_get_index(dst, nm_ip6_config_get_nameserver(src, i)); if (idx >= 0) nm_ip6_config_del_nameserver(dst, idx); } /* routes */ changed = FALSE; changed_default_route = FALSE; nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, src, &r) { const NMPObject * o_src = NMP_OBJECT_UP_CAST(r); NMPObject o_lookup_copy; const NMPObject * o_lookup; nm_auto_nmpobj const NMPObject *obj_old = NULL; if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r) && default_route_metric_penalty) { NMPlatformIP6Route *rr; /* the default route was penalized when merging it to the combined ip-config. * When subtracting the routes, we must re-do that process when comparing * the routes. */ o_lookup = nmp_object_stackinit_obj(&o_lookup_copy, o_src); rr = NMP_OBJECT_CAST_IP6_ROUTE(&o_lookup_copy); rr->metric = nm_utils_ip_route_metric_penalize(rr->metric, default_route_metric_penalty); } else o_lookup = o_src; if (nm_dedup_multi_index_remove_obj(dst_priv->multi_idx, &dst_priv->idx_ip6_routes, o_lookup, (gconstpointer *) &obj_old)) { if (dst_priv->best_default_route == obj_old) { nm_clear_nmp_object(&dst_priv->best_default_route); changed_default_route = TRUE; } changed = TRUE; } } if (changed_default_route) { nmp_object_ref_set(&dst_priv->best_default_route, _nm_ip6_config_best_default_route_find(dst)); _notify(dst, PROP_GATEWAY); } if (changed) _notify_routes(dst); /* domains */ for (i = 0; i < nm_ip6_config_get_num_domains(src); i++) { idx = _domains_get_index(dst, nm_ip6_config_get_domain(src, i)); if (idx >= 0) nm_ip6_config_del_domain(dst, idx); } /* dns searches */ for (i = 0; i < nm_ip6_config_get_num_searches(src); i++) { idx = _searches_get_index(dst, nm_ip6_config_get_search(src, i)); if (idx >= 0) nm_ip6_config_del_search(dst, idx); } /* dns options */ for (i = 0; i < nm_ip6_config_get_num_dns_options(src); i++) { idx = _dns_options_get_index(dst, nm_ip6_config_get_dns_option(src, i)); if (idx >= 0) nm_ip6_config_del_dns_option(dst, idx); } /* DNS priority */ if (nm_ip6_config_get_dns_priority(src) == nm_ip6_config_get_dns_priority(dst)) nm_ip6_config_set_dns_priority(dst, 0); g_object_thaw_notify(G_OBJECT(dst)); } static gboolean _nm_ip6_config_intersect_helper(NMIP6Config * dst, const NMIP6Config *src, gboolean intersect_addresses, gboolean intersect_routes, guint32 default_route_metric_penalty, gboolean update_dst) { NMIP6ConfigPrivate * dst_priv; const NMIP6ConfigPrivate * src_priv; NMDedupMultiIter ipconf_iter; const NMPlatformIP6Address *a; const NMPlatformIP6Route * r; gboolean changed, result = FALSE; const NMPObject * new_best_default_route; g_return_val_if_fail(src, FALSE); g_return_val_if_fail(dst, FALSE); dst_priv = NM_IP6_CONFIG_GET_PRIVATE(dst); src_priv = NM_IP6_CONFIG_GET_PRIVATE(src); if (update_dst) g_object_freeze_notify(G_OBJECT(dst)); /* addresses */ if (intersect_addresses) { changed = FALSE; nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, dst, &a) { if (nm_dedup_multi_index_lookup_obj(src_priv->multi_idx, &src_priv->idx_ip6_addresses, NMP_OBJECT_UP_CAST(a))) continue; if (!update_dst) return TRUE; if (nm_dedup_multi_index_remove_entry(dst_priv->multi_idx, ipconf_iter.current) != 1) nm_assert_not_reached(); changed = TRUE; } if (changed) { _notify_addresses(dst); result = TRUE; } } /* ignore nameservers */ /* routes */ if (!intersect_routes) goto skip_routes; changed = FALSE; new_best_default_route = NULL; nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, dst, &r) { const NMPObject *o_dst = NMP_OBJECT_UP_CAST(r); const NMPObject *o_lookup; NMPObject o_lookup_copy; if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r) && default_route_metric_penalty) { NMPlatformIP6Route *rr; /* the default route was penalized when merging it to the combined ip-config. * When intersecting the routes, we must re-do that process when comparing * the routes. */ o_lookup = nmp_object_stackinit_obj(&o_lookup_copy, o_dst); rr = NMP_OBJECT_CAST_IP6_ROUTE(&o_lookup_copy); rr->metric = nm_utils_ip_route_metric_penalize(rr->metric, default_route_metric_penalty); } else o_lookup = o_dst; if (nm_dedup_multi_index_lookup_obj(src_priv->multi_idx, &src_priv->idx_ip6_routes, o_lookup)) { new_best_default_route = _nm_ip_config_best_default_route_find_better(new_best_default_route, o_dst); continue; } if (!update_dst) return TRUE; if (nm_dedup_multi_index_remove_entry(dst_priv->multi_idx, ipconf_iter.current) != 1) nm_assert_not_reached(); changed = TRUE; } if (nmp_object_ref_set(&dst_priv->best_default_route, new_best_default_route)) { nm_assert(changed); _notify(dst, PROP_GATEWAY); } if (changed) { _notify_routes(dst); result = TRUE; } skip_routes: /* ignore domains */ /* ignore dns searches */ /* ignore dns options */ if (update_dst) g_object_thaw_notify(G_OBJECT(dst)); return result; } /** * nm_ip6_config_intersect: * @dst: a configuration to be updated * @src: another configuration * @intersect_addresses: whether addresses should be intersected * @intersect_routes: whether routes should be intersected * @default_route_metric_penalty: the default route metric penalty * * Computes the intersection between @src and @dst and updates @dst in place * with the result. */ void nm_ip6_config_intersect(NMIP6Config * dst, const NMIP6Config *src, gboolean intersect_addresses, gboolean intersect_routes, guint32 default_route_metric_penalty) { _nm_ip6_config_intersect_helper(dst, src, intersect_addresses, intersect_routes, default_route_metric_penalty, TRUE); } /** * nm_ip6_config_intersect_alloc: * @a: a configuration * @b: another configuration * @intersect_addresses: whether addresses should be intersected * @intersect_routes: whether routes should be intersected * @default_route_metric_penalty: the default route metric penalty * * Computes the intersection between @a and @b and returns the result in a newly * allocated configuration. As a special case, if @a and @b are identical (with * respect to the only properties considered - addresses and routes) the * functions returns NULL so that one of existing configuration can be reused * without allocation. * * Returns: the intersection between @a and @b, or %NULL if the result is equal * to @a and @b. */ NMIP6Config * nm_ip6_config_intersect_alloc(const NMIP6Config *a, const NMIP6Config *b, gboolean intersect_addresses, gboolean intersect_routes, guint32 default_route_metric_penalty) { NMIP6Config *a_copy; if (_nm_ip6_config_intersect_helper((NMIP6Config *) a, b, intersect_addresses, intersect_routes, default_route_metric_penalty, FALSE)) { a_copy = nm_ip6_config_clone(a); _nm_ip6_config_intersect_helper(a_copy, b, intersect_addresses, intersect_routes, default_route_metric_penalty, TRUE); return a_copy; } else return NULL; } /** * nm_ip6_config_replace: * @dst: config which will be replaced with everything in @src * @src: config to copy over to @dst * @relevant_changes: return whether there are changes to the * destination object that are relevant. This is equal to * nm_ip6_config_equal() showing any difference. * * Replaces everything in @dst with @src so that the two configurations * contain the same content -- with the exception of the dbus path. * * Returns: whether the @dst instance changed in any way (including minor changes, * that are not signaled by the output parameter @relevant_changes). */ gboolean nm_ip6_config_replace(NMIP6Config *dst, const NMIP6Config *src, gboolean *relevant_changes) { #if NM_MORE_ASSERTS gboolean config_equal; #endif gboolean has_minor_changes = FALSE, has_relevant_changes = FALSE, are_equal; guint i, num; NMIP6ConfigPrivate * dst_priv; const NMIP6ConfigPrivate * src_priv; NMDedupMultiIter ipconf_iter_src, ipconf_iter_dst; const NMDedupMultiHeadEntry *head_entry_src; const NMPObject * new_best_default_route; g_return_val_if_fail(NM_IS_IP6_CONFIG(src), FALSE); g_return_val_if_fail(NM_IS_IP6_CONFIG(dst), FALSE); g_return_val_if_fail(src != dst, FALSE); #if NM_MORE_ASSERTS config_equal = nm_ip6_config_equal(dst, src); #endif dst_priv = NM_IP6_CONFIG_GET_PRIVATE(dst); src_priv = NM_IP6_CONFIG_GET_PRIVATE(src); g_return_val_if_fail(src_priv->ifindex > 0, FALSE); g_object_freeze_notify(G_OBJECT(dst)); /* ifindex */ if (src_priv->ifindex != dst_priv->ifindex) { dst_priv->ifindex = src_priv->ifindex; has_minor_changes = TRUE; } /* addresses */ head_entry_src = nm_ip6_config_lookup_addresses(src); nm_dedup_multi_iter_init(&ipconf_iter_src, head_entry_src); nm_ip_config_iter_ip6_address_init(&ipconf_iter_dst, dst); are_equal = TRUE; while (TRUE) { gboolean has; const NMPlatformIP6Address *r_src = NULL; const NMPlatformIP6Address *r_dst = NULL; has = nm_platform_dedup_multi_iter_next_ip6_address(&ipconf_iter_src, &r_src); if (has != nm_platform_dedup_multi_iter_next_ip6_address(&ipconf_iter_dst, &r_dst)) { are_equal = FALSE; has_relevant_changes = TRUE; break; } if (!has) break; if (nm_platform_ip6_address_cmp(r_src, r_dst) != 0) { are_equal = FALSE; if (!IN6_ARE_ADDR_EQUAL(&r_src->address, &r_dst->address) || r_src->plen != r_dst->plen || !IN6_ARE_ADDR_EQUAL(nm_platform_ip6_address_get_peer(r_src), nm_platform_ip6_address_get_peer(r_dst))) { has_relevant_changes = TRUE; break; } } } if (!are_equal) { has_minor_changes = TRUE; nm_dedup_multi_index_dirty_set_idx(dst_priv->multi_idx, &dst_priv->idx_ip6_addresses); nm_dedup_multi_iter_for_each (&ipconf_iter_src, head_entry_src) { _nm_ip_config_add_obj(dst_priv->multi_idx, &dst_priv->idx_ip6_addresses_, dst_priv->ifindex, ipconf_iter_src.current->obj, NULL, FALSE, TRUE, NULL, NULL); } nm_dedup_multi_index_dirty_remove_idx(dst_priv->multi_idx, &dst_priv->idx_ip6_addresses, FALSE); _notify_addresses(dst); } /* routes */ head_entry_src = nm_ip6_config_lookup_routes(src); nm_dedup_multi_iter_init(&ipconf_iter_src, head_entry_src); nm_ip_config_iter_ip6_route_init(&ipconf_iter_dst, dst); are_equal = TRUE; while (TRUE) { gboolean has; const NMPlatformIP6Route *r_src = NULL; const NMPlatformIP6Route *r_dst = NULL; has = nm_platform_dedup_multi_iter_next_ip6_route(&ipconf_iter_src, &r_src); if (has != nm_platform_dedup_multi_iter_next_ip6_route(&ipconf_iter_dst, &r_dst)) { are_equal = FALSE; has_relevant_changes = TRUE; break; } if (!has) break; if (nm_platform_ip6_route_cmp_full(r_src, r_dst) != 0) { are_equal = FALSE; if (r_src->plen != r_dst->plen || !nm_utils_ip6_address_same_prefix(&r_src->network, &r_dst->network, r_src->plen) || r_src->metric != r_dst->metric || !IN6_ARE_ADDR_EQUAL(&r_src->gateway, &r_dst->gateway)) { has_relevant_changes = TRUE; break; } } } if (!are_equal) { has_minor_changes = TRUE; new_best_default_route = NULL; nm_dedup_multi_index_dirty_set_idx(dst_priv->multi_idx, &dst_priv->idx_ip6_routes); nm_dedup_multi_iter_for_each (&ipconf_iter_src, head_entry_src) { const NMPObject *o = ipconf_iter_src.current->obj; const NMPObject *obj_new; _nm_ip_config_add_obj(dst_priv->multi_idx, &dst_priv->idx_ip6_routes_, dst_priv->ifindex, o, NULL, FALSE, TRUE, NULL, &obj_new); new_best_default_route = _nm_ip_config_best_default_route_find_better(new_best_default_route, obj_new); } nm_dedup_multi_index_dirty_remove_idx(dst_priv->multi_idx, &dst_priv->idx_ip6_routes, FALSE); if (nmp_object_ref_set(&dst_priv->best_default_route, new_best_default_route)) _notify(dst, PROP_GATEWAY); _notify_routes(dst); } /* nameservers */ num = nm_ip6_config_get_num_nameservers(src); are_equal = num == nm_ip6_config_get_num_nameservers(dst); if (are_equal) { for (i = 0; i < num; i++) { if (!IN6_ARE_ADDR_EQUAL(nm_ip6_config_get_nameserver(src, i), nm_ip6_config_get_nameserver(dst, i))) { are_equal = FALSE; break; } } } if (!are_equal) { nm_ip6_config_reset_nameservers(dst); for (i = 0; i < num; i++) nm_ip6_config_add_nameserver(dst, nm_ip6_config_get_nameserver(src, i)); has_relevant_changes = TRUE; } /* domains */ num = nm_ip6_config_get_num_domains(src); are_equal = num == nm_ip6_config_get_num_domains(dst); if (are_equal) { for (i = 0; i < num; i++) { if (g_strcmp0(nm_ip6_config_get_domain(src, i), nm_ip6_config_get_domain(dst, i))) { are_equal = FALSE; break; } } } if (!are_equal) { nm_ip6_config_reset_domains(dst); for (i = 0; i < num; i++) nm_ip6_config_add_domain(dst, nm_ip6_config_get_domain(src, i)); has_relevant_changes = TRUE; } /* dns searches */ num = nm_ip6_config_get_num_searches(src); are_equal = num == nm_ip6_config_get_num_searches(dst); if (are_equal) { for (i = 0; i < num; i++) { if (g_strcmp0(nm_ip6_config_get_search(src, i), nm_ip6_config_get_search(dst, i))) { are_equal = FALSE; break; } } } if (!are_equal) { nm_ip6_config_reset_searches(dst); for (i = 0; i < num; i++) nm_ip6_config_add_search(dst, nm_ip6_config_get_search(src, i)); has_relevant_changes = TRUE; } /* dns options */ num = nm_ip6_config_get_num_dns_options(src); are_equal = num == nm_ip6_config_get_num_dns_options(dst); if (are_equal) { for (i = 0; i < num; i++) { if (g_strcmp0(nm_ip6_config_get_dns_option(src, i), nm_ip6_config_get_dns_option(dst, i))) { are_equal = FALSE; break; } } } if (!are_equal) { nm_ip6_config_reset_dns_options(dst); for (i = 0; i < num; i++) nm_ip6_config_add_dns_option(dst, nm_ip6_config_get_dns_option(src, i)); has_relevant_changes = TRUE; } /* DNS priority */ if (src_priv->dns_priority != dst_priv->dns_priority) { nm_ip6_config_set_dns_priority(dst, src_priv->dns_priority); has_minor_changes = TRUE; } if (src_priv->privacy != dst_priv->privacy) { nm_ip6_config_set_privacy(dst, src_priv->privacy); has_minor_changes = TRUE; } #if NM_MORE_ASSERTS /* config_equal does not compare *all* the fields, therefore, we might have has_minor_changes * regardless of config_equal. But config_equal must correspond to has_relevant_changes. */ nm_assert(config_equal == !has_relevant_changes); #endif g_object_thaw_notify(G_OBJECT(dst)); if (relevant_changes) *relevant_changes = has_relevant_changes; return has_relevant_changes || has_minor_changes; } /*****************************************************************************/ void nm_ip6_config_reset_addresses_ndisc(NMIP6Config * self, const NMNDiscAddress *addresses, guint addresses_n, guint8 plen, guint32 ifa_flags) { NMIP6ConfigPrivate *priv; guint i; gboolean changed = FALSE; gint32 base_time_sec; g_return_if_fail(NM_IS_IP6_CONFIG(self)); priv = NM_IP6_CONFIG_GET_PRIVATE(self); g_return_if_fail(priv->ifindex > 0); /* the base-timestamp doesn't matter it's only an anchor for the * expiry. However, try to re-use the same base-time for a while * by rounding it to 10000 seconds. * * That is because we deduplicate and NMPlatformIP6Address instances * so using the same timestamps is preferable. */ base_time_sec = nm_utils_get_monotonic_timestamp_sec(); base_time_sec = (base_time_sec / 10000) * 10000; base_time_sec = NM_MAX(1, base_time_sec); nm_dedup_multi_index_dirty_set_idx(priv->multi_idx, &priv->idx_ip6_addresses); for (i = 0; i < addresses_n; i++) { const NMNDiscAddress *ndisc_addr = &addresses[i]; NMPObject obj; NMPlatformIP6Address *a; nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP6_ADDRESS, NULL); a = NMP_OBJECT_CAST_IP6_ADDRESS(&obj); a->ifindex = priv->ifindex; a->address = ndisc_addr->address; a->plen = plen; a->timestamp = base_time_sec, a->lifetime = _nm_ndisc_lifetime_from_expiry(((gint64) base_time_sec) * 1000, ndisc_addr->expiry_msec, TRUE), a->preferred = _nm_ndisc_lifetime_from_expiry(((gint64) base_time_sec) * 1000, ndisc_addr->expiry_preferred_msec, TRUE), a->addr_source = NM_IP_CONFIG_SOURCE_NDISC; a->n_ifa_flags = ifa_flags; if (_nm_ip_config_add_obj(priv->multi_idx, &priv->idx_ip6_addresses_, priv->ifindex, &obj, NULL, FALSE, TRUE, NULL, NULL)) changed = TRUE; } if (nm_dedup_multi_index_dirty_remove_idx(priv->multi_idx, &priv->idx_ip6_addresses, FALSE) > 0) changed = TRUE; if (changed) _notify_addresses(self); } void nm_ip6_config_reset_addresses(NMIP6Config *self) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); if (nm_dedup_multi_index_remove_idx(priv->multi_idx, &priv->idx_ip6_addresses) > 0) _notify_addresses(self); } static void _add_address(NMIP6Config *self, const NMPObject *obj_new, const NMPlatformIP6Address *new) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); if (_nm_ip_config_add_obj(priv->multi_idx, &priv->idx_ip6_addresses_, priv->ifindex, obj_new, (const NMPlatformObject *) new, TRUE, FALSE, NULL, NULL)) _notify_addresses(self); } /** * nm_ip6_config_add_address: * @self: the #NMIP6Config * @new: the new address to add to @self * * Adds the new address to @self. If an address with the same basic properties * (address, prefix) already exists in @self, it is overwritten with the * lifetime and preferred of @new. The source is also overwritten by the source * from @new if that source is higher priority. */ void nm_ip6_config_add_address(NMIP6Config *self, const NMPlatformIP6Address *new) { g_return_if_fail(self); g_return_if_fail(new); g_return_if_fail(new->plen <= 128); g_return_if_fail(NM_IP6_CONFIG_GET_PRIVATE(self)->ifindex > 0); _add_address(self, NULL, new); } void _nmtst_ip6_config_del_address(NMIP6Config *self, guint i) { const NMPlatformIP6Address *a; a = _nmtst_ip6_config_get_address(self, i); if (!nm_ip6_config_nmpobj_remove(self, NMP_OBJECT_UP_CAST(a))) g_assert_not_reached(); } guint nm_ip6_config_get_num_addresses(const NMIP6Config *self) { const NMDedupMultiHeadEntry *head_entry; head_entry = nm_ip6_config_lookup_addresses(self); return head_entry ? head_entry->len : 0; } const NMPlatformIP6Address * nm_ip6_config_get_first_address(const NMIP6Config *self) { NMDedupMultiIter iter; const NMPlatformIP6Address *a = NULL; nm_ip_config_iter_ip6_address_for_each (&iter, self, &a) return a; return NULL; } const NMPlatformIP6Address * _nmtst_ip6_config_get_address(const NMIP6Config *self, guint i) { NMDedupMultiIter iter; const NMPlatformIP6Address *a = NULL; guint j; j = 0; nm_ip_config_iter_ip6_address_for_each (&iter, self, &a) { if (i == j) return a; j++; } g_return_val_if_reached(NULL); } const NMPlatformIP6Address * nm_ip6_config_lookup_address(const NMIP6Config *self, const struct in6_addr *addr) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); NMPObject obj_stack; const NMDedupMultiEntry * entry; nmp_object_stackinit_id_ip6_address(&obj_stack, priv->ifindex, addr); entry = nm_dedup_multi_index_lookup_obj(priv->multi_idx, &priv->idx_ip6_addresses, &obj_stack); return entry ? NMP_OBJECT_CAST_IP6_ADDRESS(entry->obj) : NULL; } const NMPlatformIP6Address * nm_ip6_config_find_first_address(const NMIP6Config *self, NMPlatformMatchFlags match_flag) { const NMPlatformIP6Address *addr; NMDedupMultiIter iter; g_return_val_if_fail(NM_IS_IP6_CONFIG(self), NULL); nm_assert(!NM_FLAGS_ANY( match_flag, ~(NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY))); nm_assert(NM_FLAGS_ANY(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY)); nm_assert(NM_FLAGS_ANY(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY)); nm_ip_config_iter_ip6_address_for_each (&iter, self, &addr) { if (nm_platform_ip6_address_match(addr, match_flag)) return addr; } return NULL; } /** * nm_ip6_config_has_dad_pending_addresses * @self: configuration containing the addresses to check * @candidates: configuration with the list of addresses we are * interested in * * Check whether there are addresses with DAD pending in @self, that * are also contained in @candidates. * * Returns: %TRUE if at least one matching address was found, %FALSE * otherwise */ gboolean nm_ip6_config_has_any_dad_pending(const NMIP6Config *self, const NMIP6Config *candidates) { NMDedupMultiIter ipconf_iter; const NMPlatformIP6Address *addr, *addr_c; nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, self, &addr) { if (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TENTATIVE) && !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_DADFAILED) && !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_OPTIMISTIC)) { addr_c = nm_ip6_config_lookup_address(candidates, &addr->address); if (addr_c) { if (addr->plen == addr_c->plen) return TRUE; } } } return FALSE; } /*****************************************************************************/ static const NMDedupMultiEntry * _lookup_route(const NMIP6Config *self, const NMPObject *needle, NMPlatformIPRouteCmpType cmp_type) { const NMIP6ConfigPrivate *priv; nm_assert(NM_IS_IP6_CONFIG(self)); nm_assert(NMP_OBJECT_GET_TYPE(needle) == NMP_OBJECT_TYPE_IP6_ROUTE); priv = NM_IP6_CONFIG_GET_PRIVATE(self); return _nm_ip_config_lookup_ip_route(priv->multi_idx, &priv->idx_ip6_routes_, needle, cmp_type); } void nm_ip6_config_reset_routes_ndisc(NMIP6Config * self, const NMNDiscGateway *gateways, guint gateways_n, const NMNDiscRoute * routes, guint routes_n, guint32 route_table, guint32 route_metric, gboolean kernel_support_rta_pref) { NMIP6ConfigPrivate *priv; guint i; gboolean changed = FALSE; const NMPObject * new_best_default_route; g_return_if_fail(NM_IS_IP6_CONFIG(self)); priv = NM_IP6_CONFIG_GET_PRIVATE(self); g_return_if_fail(priv->ifindex > 0); nm_dedup_multi_index_dirty_set_idx(priv->multi_idx, &priv->idx_ip6_routes); new_best_default_route = NULL; for (i = 0; i < routes_n; i++) { const NMNDiscRoute *ndisc_route = &routes[i]; NMPObject obj; const NMPObject * obj_new; NMPlatformIP6Route *r; nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP6_ROUTE, NULL); r = NMP_OBJECT_CAST_IP6_ROUTE(&obj); r->ifindex = priv->ifindex; r->network = ndisc_route->network; r->plen = ndisc_route->plen; r->gateway = ndisc_route->gateway; r->rt_source = NM_IP_CONFIG_SOURCE_NDISC; r->table_coerced = nm_platform_route_table_coerce(route_table); r->metric = route_metric; r->rt_pref = ndisc_route->preference; nm_assert((NMIcmpv6RouterPref) r->rt_pref == ndisc_route->preference); if (_nm_ip_config_add_obj(priv->multi_idx, &priv->idx_ip6_routes_, priv->ifindex, &obj, NULL, FALSE, TRUE, NULL, &obj_new)) changed = TRUE; new_best_default_route = _nm_ip_config_best_default_route_find_better(new_best_default_route, obj_new); } if (gateways_n) { const NMPObject * obj_new; NMPlatformIP6Route r = { .rt_source = NM_IP_CONFIG_SOURCE_NDISC, .ifindex = priv->ifindex, .table_coerced = nm_platform_route_table_coerce(route_table), .metric = route_metric, }; const NMIcmpv6RouterPref first_pref = gateways[0].preference; for (i = 0; i < gateways_n; i++) { r.gateway = gateways[i].address; r.rt_pref = gateways[i].preference; nm_assert((NMIcmpv6RouterPref) r.rt_pref == gateways[i].preference); if (_nm_ip_config_add_obj(priv->multi_idx, &priv->idx_ip6_routes_, priv->ifindex, NULL, (const NMPlatformObject *) &r, FALSE, TRUE, NULL, &obj_new)) changed = TRUE; new_best_default_route = _nm_ip_config_best_default_route_find_better(new_best_default_route, obj_new); if (first_pref != gateways[i].preference && !kernel_support_rta_pref) { /* We are unable to configure a router preference. Hence, we skip all gateways * with a different preference from the first gateway. Note, that the gateways * are sorted in order of highest to lowest preference. */ break; } } } if (nm_dedup_multi_index_dirty_remove_idx(priv->multi_idx, &priv->idx_ip6_routes, FALSE) > 0) changed = TRUE; if (nmp_object_ref_set(&priv->best_default_route, new_best_default_route)) { changed = TRUE; _notify(self, PROP_GATEWAY); } if (changed) _notify_routes(self); } void nm_ip6_config_reset_routes(NMIP6Config *self) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); if (nm_dedup_multi_index_remove_idx(priv->multi_idx, &priv->idx_ip6_routes) > 0) { if (nm_clear_nmp_object(&priv->best_default_route)) _notify(self, PROP_GATEWAY); _notify_routes(self); } } static void _add_route(NMIP6Config * self, const NMPObject *obj_new, const NMPlatformIP6Route *new, const NMPObject **out_obj_new) { NMIP6ConfigPrivate * priv = NM_IP6_CONFIG_GET_PRIVATE(self); nm_auto_nmpobj const NMPObject *obj_old = NULL; const NMPObject * obj_new_2; nm_assert((!new) != (!obj_new)); nm_assert(!new || _route_valid(new)); nm_assert(!obj_new || _route_valid(NMP_OBJECT_CAST_IP6_ROUTE(obj_new))); if (_nm_ip_config_add_obj(priv->multi_idx, &priv->idx_ip6_routes_, priv->ifindex, obj_new, (const NMPlatformObject *) new, TRUE, FALSE, &obj_old, &obj_new_2)) { gboolean changed_default_route = FALSE; if (priv->best_default_route == obj_old && obj_old != obj_new_2) { changed_default_route = TRUE; nm_clear_nmp_object(&priv->best_default_route); } NM_SET_OUT(out_obj_new, nmp_object_ref(obj_new_2)); if (_nm_ip_config_best_default_route_merge(&priv->best_default_route, obj_new_2)) changed_default_route = TRUE; if (changed_default_route) _notify(self, PROP_GATEWAY); _notify_routes(self); } else NM_SET_OUT(out_obj_new, nmp_object_ref(obj_new_2)); } /** * nm_ip6_config_add_route: * @self: the #NMIP6Config * @new: the new route to add to @self * @out_obj_new: (allow-none) (out): the added route object. Must be unrefed * by caller. * * Adds the new route to @self. If a route with the same basic properties * (network, prefix) already exists in @self, it is overwritten including the * gateway and metric of @new. The source is also overwritten by the source * from @new if that source is higher priority. */ void nm_ip6_config_add_route(NMIP6Config *self, const NMPlatformIP6Route *new, const NMPObject **out_obj_new) { g_return_if_fail(self); g_return_if_fail(new); g_return_if_fail(new->plen <= 128); g_return_if_fail(NM_IP6_CONFIG_GET_PRIVATE(self)->ifindex > 0); _add_route(self, NULL, new, out_obj_new); } void _nmtst_ip6_config_del_route(NMIP6Config *self, guint i) { const NMPlatformIP6Route *r; r = _nmtst_ip6_config_get_route(self, i); if (!nm_ip6_config_nmpobj_remove(self, NMP_OBJECT_UP_CAST(r))) g_assert_not_reached(); } guint nm_ip6_config_get_num_routes(const NMIP6Config *self) { const NMDedupMultiHeadEntry *head_entry; head_entry = nm_ip6_config_lookup_routes(self); nm_assert(!head_entry || head_entry->len == c_list_length(&head_entry->lst_entries_head)); return head_entry ? head_entry->len : 0; } const NMPlatformIP6Route * _nmtst_ip6_config_get_route(const NMIP6Config *self, guint i) { NMDedupMultiIter iter; const NMPlatformIP6Route *r = NULL; guint j; j = 0; nm_ip_config_iter_ip6_route_for_each (&iter, self, &r) { if (i == j) return r; j++; } g_return_val_if_reached(NULL); } const NMPlatformIP6Route * nm_ip6_config_get_direct_route_for_host(const NMIP6Config * self, const struct in6_addr *host, guint32 route_table) { const NMPlatformIP6Route *best_route = NULL; const NMPlatformIP6Route *item; NMDedupMultiIter ipconf_iter; g_return_val_if_fail(host && !IN6_IS_ADDR_UNSPECIFIED(host), NULL); nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, &item) { if (!IN6_IS_ADDR_UNSPECIFIED(&item->gateway)) continue; if (best_route && best_route->plen > item->plen) continue; if (nm_platform_route_table_uncoerce(item->table_coerced, TRUE) != route_table) continue; if (!nm_utils_ip6_address_same_prefix(host, &item->network, item->plen)) continue; if (best_route && best_route->metric <= item->metric) continue; best_route = item; } return best_route; } const NMPlatformIP6Address * nm_ip6_config_get_subnet_for_host(const NMIP6Config *self, const struct in6_addr *host) { NMDedupMultiIter iter; const NMPlatformIP6Address *item; const NMPlatformIP6Address *subnet = NULL; struct in6_addr subnet2, host2; g_return_val_if_fail(host && !IN6_IS_ADDR_UNSPECIFIED(host), NULL); nm_ip_config_iter_ip6_address_for_each (&iter, self, &item) { if (subnet && subnet->plen >= item->plen) continue; nm_utils_ip6_address_clear_host_address(&host2, host, item->plen); nm_utils_ip6_address_clear_host_address(&subnet2, &item->address, item->plen); if (IN6_ARE_ADDR_EQUAL(&subnet2, &host2)) subnet = item; } return subnet; } /*****************************************************************************/ void nm_ip6_config_reset_nameservers(NMIP6Config *self) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); if (priv->nameservers->len != 0) { g_array_set_size(priv->nameservers, 0); _notify(self, PROP_NAMESERVERS); } } void nm_ip6_config_add_nameserver(NMIP6Config *self, const struct in6_addr *new) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); int i; g_return_if_fail(new != NULL); for (i = 0; i < priv->nameservers->len; i++) if (IN6_ARE_ADDR_EQUAL(new, &g_array_index(priv->nameservers, struct in6_addr, i))) return; g_array_append_val(priv->nameservers, *new); _notify(self, PROP_NAMESERVERS); } void nm_ip6_config_del_nameserver(NMIP6Config *self, guint i) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); g_return_if_fail(i < priv->nameservers->len); g_array_remove_index(priv->nameservers, i); _notify(self, PROP_NAMESERVERS); } guint nm_ip6_config_get_num_nameservers(const NMIP6Config *self) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); return priv->nameservers->len; } const struct in6_addr * nm_ip6_config_get_nameserver(const NMIP6Config *self, guint i) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); return &g_array_index(priv->nameservers, struct in6_addr, i); } /*****************************************************************************/ void nm_ip6_config_reset_domains(NMIP6Config *self) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); if (priv->domains->len != 0) { g_ptr_array_set_size(priv->domains, 0); _notify(self, PROP_DOMAINS); } } void nm_ip6_config_add_domain(NMIP6Config *self, const char *domain) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); if (_nm_ip_config_check_and_add_domain(priv->domains, domain)) _notify(self, PROP_DOMAINS); } void nm_ip6_config_del_domain(NMIP6Config *self, guint i) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); g_return_if_fail(i < priv->domains->len); g_ptr_array_remove_index(priv->domains, i); _notify(self, PROP_DOMAINS); } guint nm_ip6_config_get_num_domains(const NMIP6Config *self) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); return priv->domains->len; } const char * nm_ip6_config_get_domain(const NMIP6Config *self, guint i) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); return g_ptr_array_index(priv->domains, i); } /*****************************************************************************/ void nm_ip6_config_reset_searches(NMIP6Config *self) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); if (priv->searches->len != 0) { g_ptr_array_set_size(priv->searches, 0); _notify(self, PROP_SEARCHES); } } void nm_ip6_config_add_search(NMIP6Config *self, const char *search) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); if (_nm_ip_config_check_and_add_domain(priv->searches, search)) _notify(self, PROP_SEARCHES); } void nm_ip6_config_del_search(NMIP6Config *self, guint i) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); g_return_if_fail(i < priv->searches->len); g_ptr_array_remove_index(priv->searches, i); _notify(self, PROP_SEARCHES); } guint nm_ip6_config_get_num_searches(const NMIP6Config *self) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); return priv->searches->len; } const char * nm_ip6_config_get_search(const NMIP6Config *self, guint i) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); return g_ptr_array_index(priv->searches, i); } /*****************************************************************************/ void nm_ip6_config_reset_dns_options(NMIP6Config *self) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); if (priv->dns_options->len != 0) { g_ptr_array_set_size(priv->dns_options, 0); _notify(self, PROP_DNS_OPTIONS); } } void nm_ip6_config_add_dns_option(NMIP6Config *self, const char *new) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); int i; g_return_if_fail(new != NULL); g_return_if_fail(new[0] != '\0'); for (i = 0; i < priv->dns_options->len; i++) if (!g_strcmp0(g_ptr_array_index(priv->dns_options, i), new)) return; g_ptr_array_add(priv->dns_options, g_strdup(new)); _notify(self, PROP_DNS_OPTIONS); } void nm_ip6_config_del_dns_option(NMIP6Config *self, guint i) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); g_return_if_fail(i < priv->dns_options->len); g_ptr_array_remove_index(priv->dns_options, i); _notify(self, PROP_DNS_OPTIONS); } guint nm_ip6_config_get_num_dns_options(const NMIP6Config *self) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); return priv->dns_options->len; } const char * nm_ip6_config_get_dns_option(const NMIP6Config *self, guint i) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); return g_ptr_array_index(priv->dns_options, i); } /*****************************************************************************/ NMIPConfigFlags nm_ip6_config_get_config_flags(const NMIP6Config *self) { return NM_IP6_CONFIG_GET_PRIVATE(self)->config_flags; } void nm_ip6_config_set_config_flags(NMIP6Config *self, NMIPConfigFlags flags, NMIPConfigFlags mask) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); if (mask == 0) { /* for convenience, accept 0 mask to set any flags. */ mask = flags; } nm_assert(!NM_FLAGS_ANY(flags, ~mask)); priv->config_flags = (flags & mask) | (priv->config_flags & ~mask); } /*****************************************************************************/ void nm_ip6_config_set_dns_priority(NMIP6Config *self, int priority) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); if (priority != priv->dns_priority) { priv->dns_priority = priority; _notify(self, PROP_DNS_PRIORITY); } } int nm_ip6_config_get_dns_priority(const NMIP6Config *self) { const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); return priv->dns_priority; } /*****************************************************************************/ const NMPObject * nm_ip6_config_nmpobj_lookup(const NMIP6Config *self, const NMPObject *needle) { const NMIP6ConfigPrivate * priv; const NMDedupMultiIdxType *idx_type; g_return_val_if_fail(NM_IS_IP6_CONFIG(self), NULL); priv = NM_IP6_CONFIG_GET_PRIVATE(self); switch (NMP_OBJECT_GET_TYPE(needle)) { case NMP_OBJECT_TYPE_IP6_ADDRESS: idx_type = &priv->idx_ip6_addresses; break; case NMP_OBJECT_TYPE_IP6_ROUTE: idx_type = &priv->idx_ip6_routes; break; default: g_return_val_if_reached(NULL); } return nm_dedup_multi_entry_get_obj( nm_dedup_multi_index_lookup_obj(priv->multi_idx, idx_type, needle)); } gboolean nm_ip6_config_nmpobj_remove(NMIP6Config *self, const NMPObject *needle) { NMIP6ConfigPrivate * priv; NMDedupMultiIdxType *idx_type; nm_auto_nmpobj const NMPObject *obj_old = NULL; guint n; g_return_val_if_fail(NM_IS_IP6_CONFIG(self), FALSE); priv = NM_IP6_CONFIG_GET_PRIVATE(self); switch (NMP_OBJECT_GET_TYPE(needle)) { case NMP_OBJECT_TYPE_IP6_ADDRESS: idx_type = &priv->idx_ip6_addresses; break; case NMP_OBJECT_TYPE_IP6_ROUTE: idx_type = &priv->idx_ip6_routes; break; default: g_return_val_if_reached(FALSE); } n = nm_dedup_multi_index_remove_obj(priv->multi_idx, idx_type, needle, (gconstpointer *) &obj_old); if (n != 1) { nm_assert(n == 0); return FALSE; } nm_assert(NMP_OBJECT_GET_TYPE(obj_old) == NMP_OBJECT_GET_TYPE(needle)); switch (NMP_OBJECT_GET_TYPE(obj_old)) { case NMP_OBJECT_TYPE_IP6_ADDRESS: _notify_addresses(self); break; case NMP_OBJECT_TYPE_IP6_ROUTE: if (priv->best_default_route == obj_old) { if (nmp_object_ref_set(&priv->best_default_route, _nm_ip6_config_best_default_route_find(self))) _notify(self, PROP_GATEWAY); } _notify_routes(self); break; default: nm_assert_not_reached(); } return TRUE; } /*****************************************************************************/ static void hash_u32(GChecksum *sum, guint32 n) { g_checksum_update(sum, (const guint8 *) &n, sizeof(n)); } static void hash_in6addr(GChecksum *sum, const struct in6_addr *a) { if (a) g_checksum_update(sum, (const guint8 *) a, sizeof(*a)); else g_checksum_update(sum, (const guint8 *) &in6addr_any, sizeof(in6addr_any)); } void nm_ip6_config_hash(const NMIP6Config *self, GChecksum *sum, gboolean dns_only) { guint32 i; const char * s; NMDedupMultiIter ipconf_iter; const NMPlatformIP6Address *address; const NMPlatformIP6Route * route; g_return_if_fail(self); g_return_if_fail(sum); if (dns_only == FALSE) { nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, self, &address) { hash_in6addr(sum, &address->address); hash_u32(sum, address->plen); } nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, &route) { hash_in6addr(sum, &route->network); hash_u32(sum, route->plen); hash_in6addr(sum, &route->gateway); hash_u32(sum, route->metric); } } for (i = 0; i < nm_ip6_config_get_num_nameservers(self); i++) hash_in6addr(sum, nm_ip6_config_get_nameserver(self, i)); for (i = 0; i < nm_ip6_config_get_num_domains(self); i++) { s = nm_ip6_config_get_domain(self, i); g_checksum_update(sum, (const guint8 *) s, strlen(s)); } for (i = 0; i < nm_ip6_config_get_num_searches(self); i++) { s = nm_ip6_config_get_search(self, i); g_checksum_update(sum, (const guint8 *) s, strlen(s)); } for (i = 0; i < nm_ip6_config_get_num_dns_options(self); i++) { s = nm_ip6_config_get_dns_option(self, i); g_checksum_update(sum, (const guint8 *) s, strlen(s)); } } /** * nm_ip6_config_equal: * @a: first config to compare * @b: second config to compare * * Compares two #NMIP6Configs for basic equality. This means that all * attributes must exist in the same order in both configs (addresses, routes, * domains, DNS servers, etc) but some attributes (address lifetimes, and address * and route sources) are ignored. * * Returns: %TRUE if the configurations are basically equal to each other, * %FALSE if not */ gboolean nm_ip6_config_equal(const NMIP6Config *a, const NMIP6Config *b) { nm_auto_free_checksum GChecksum *a_checksum = g_checksum_new(G_CHECKSUM_SHA1); nm_auto_free_checksum GChecksum *b_checksum = g_checksum_new(G_CHECKSUM_SHA1); guint8 a_data[NM_UTILS_CHECKSUM_LENGTH_SHA1]; guint8 b_data[NM_UTILS_CHECKSUM_LENGTH_SHA1]; if (a) nm_ip6_config_hash(a, a_checksum, FALSE); if (b) nm_ip6_config_hash(b, b_checksum, FALSE); nm_utils_checksum_get_digest(a_checksum, a_data); nm_utils_checksum_get_digest(b_checksum, b_data); return !memcmp(a_data, b_data, sizeof(a_data)); } /*****************************************************************************/ static void nameservers_to_gvalue(GArray *array, GValue *value) { GVariantBuilder builder; guint i = 0; g_variant_builder_init(&builder, G_VARIANT_TYPE("aay")); while (array && (i < array->len)) { struct in6_addr *addr; addr = &g_array_index(array, struct in6_addr, i++); g_variant_builder_add(&builder, "@ay", g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, addr, 16, 1)); } g_value_take_variant(value, g_variant_builder_end(&builder)); } static void get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NMIP6Config * self = NM_IP6_CONFIG(object); NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); switch (prop_id) { case PROP_IFINDEX: g_value_set_int(value, priv->ifindex); break; case PROP_ADDRESS_DATA: case PROP_ADDRESSES: nm_assert(!!priv->address_data_variant == !!priv->addresses_variant); if (!priv->address_data_variant) { nm_utils_ip_addresses_to_dbus(AF_INET6, nm_ip6_config_lookup_addresses(self), priv->best_default_route, priv->privacy, &priv->address_data_variant, &priv->addresses_variant); g_variant_ref_sink(priv->address_data_variant); g_variant_ref_sink(priv->addresses_variant); } g_value_set_variant(value, prop_id == PROP_ADDRESS_DATA ? priv->address_data_variant : priv->addresses_variant); break; case PROP_ROUTE_DATA: case PROP_ROUTES: nm_assert(!!priv->route_data_variant == !!priv->routes_variant); if (!priv->route_data_variant) { nm_utils_ip_routes_to_dbus(AF_INET6, nm_ip6_config_lookup_routes(self), &priv->route_data_variant, &priv->routes_variant); g_variant_ref_sink(priv->route_data_variant); g_variant_ref_sink(priv->routes_variant); } g_value_set_variant(value, prop_id == PROP_ROUTE_DATA ? priv->route_data_variant : priv->routes_variant); break; case PROP_GATEWAY: if (priv->best_default_route) { g_value_take_string(value, nm_utils_inet6_ntop_dup( &NMP_OBJECT_CAST_IP6_ROUTE(priv->best_default_route)->gateway)); } else g_value_set_string(value, NULL); break; case PROP_NAMESERVERS: nameservers_to_gvalue(priv->nameservers, value); break; case PROP_DOMAINS: nm_utils_g_value_set_strv(value, priv->domains); break; case PROP_SEARCHES: nm_utils_g_value_set_strv(value, priv->searches); break; case PROP_DNS_OPTIONS: nm_utils_g_value_set_strv(value, priv->dns_options); break; case PROP_DNS_PRIORITY: g_value_set_int(value, priv->dns_priority); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NMIP6Config * self = NM_IP6_CONFIG(object); NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); switch (prop_id) { case PROP_MULTI_IDX: /* construct-only */ priv->multi_idx = g_value_get_pointer(value); if (!priv->multi_idx) g_return_if_reached(); nm_dedup_multi_index_ref(priv->multi_idx); break; case PROP_IFINDEX: /* construct-only */ priv->ifindex = g_value_get_int(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /*****************************************************************************/ static void nm_ip6_config_init(NMIP6Config *self) { NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); nm_ip_config_dedup_multi_idx_type_init((NMIPConfigDedupMultiIdxType *) &priv->idx_ip6_addresses, NMP_OBJECT_TYPE_IP6_ADDRESS); nm_ip_config_dedup_multi_idx_type_init((NMIPConfigDedupMultiIdxType *) &priv->idx_ip6_routes, NMP_OBJECT_TYPE_IP6_ROUTE); priv->nameservers = g_array_new(FALSE, TRUE, sizeof(struct in6_addr)); priv->domains = g_ptr_array_new_with_free_func(g_free); priv->searches = g_ptr_array_new_with_free_func(g_free); priv->dns_options = g_ptr_array_new_with_free_func(g_free); } NMIP6Config * nm_ip6_config_new(NMDedupMultiIndex *multi_idx, int ifindex) { g_return_val_if_fail(ifindex >= -1, NULL); return g_object_new(NM_TYPE_IP6_CONFIG, NM_IP6_CONFIG_MULTI_IDX, multi_idx, NM_IP6_CONFIG_IFINDEX, ifindex, NULL); } NMIP6Config * nm_ip6_config_new_cloned(const NMIP6Config *src) { NMIP6Config *new; g_return_val_if_fail(NM_IS_IP6_CONFIG(src), NULL); new = nm_ip6_config_new(nm_ip6_config_get_multi_idx(src), nm_ip6_config_get_ifindex(src)); nm_ip6_config_replace(new, src, NULL); return new; } static void finalize(GObject *object) { NMIP6Config * self = NM_IP6_CONFIG(object); NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE(self); nm_clear_nmp_object(&priv->best_default_route); nm_dedup_multi_index_remove_idx(priv->multi_idx, &priv->idx_ip6_addresses); nm_dedup_multi_index_remove_idx(priv->multi_idx, &priv->idx_ip6_routes); nm_clear_g_variant(&priv->address_data_variant); nm_clear_g_variant(&priv->addresses_variant); nm_clear_g_variant(&priv->route_data_variant); nm_clear_g_variant(&priv->routes_variant); g_array_unref(priv->nameservers); g_ptr_array_unref(priv->domains); g_ptr_array_unref(priv->searches); g_ptr_array_unref(priv->dns_options); G_OBJECT_CLASS(nm_ip6_config_parent_class)->finalize(object); nm_dedup_multi_index_unref(priv->multi_idx); } static const NMDBusInterfaceInfoExtended interface_info_ip6_config = { .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( NM_DBUS_INTERFACE_IP6_CONFIG, .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Addresses", "a(ayuay)", NM_IP6_CONFIG_ADDRESSES), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("AddressData", "aa{sv}", NM_IP6_CONFIG_ADDRESS_DATA), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Gateway", "s", NM_IP6_CONFIG_GATEWAY), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Routes", "a(ayuayu)", NM_IP6_CONFIG_ROUTES), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("RouteData", "aa{sv}", NM_IP6_CONFIG_ROUTE_DATA), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Nameservers", "aay", NM_IP6_CONFIG_NAMESERVERS), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Domains", "as", NM_IP6_CONFIG_DOMAINS), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Searches", "as", NM_IP6_CONFIG_SEARCHES), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DnsOptions", "as", NM_IP6_CONFIG_DNS_OPTIONS), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DnsPriority", "i", NM_IP6_CONFIG_DNS_PRIORITY), ), ), .legacy_property_changed = TRUE, }; static void nm_ip6_config_class_init(NMIP6ConfigClass *klass) { GObjectClass * object_class = G_OBJECT_CLASS(klass); NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); NMIPConfigClass * ip_config_class = NM_IP_CONFIG_CLASS(klass); ip_config_class->is_ipv4 = FALSE; ip_config_class->addr_family = AF_INET6; dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/IP6Config"); dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_ip6_config); object_class->get_property = get_property; object_class->set_property = set_property; object_class->finalize = finalize; obj_properties[PROP_MULTI_IDX] = g_param_spec_pointer(NM_IP6_CONFIG_MULTI_IDX, "", "", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); obj_properties[PROP_IFINDEX] = g_param_spec_int(NM_IP6_CONFIG_IFINDEX, "", "", -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); obj_properties[PROP_ADDRESS_DATA] = g_param_spec_variant(NM_IP6_CONFIG_ADDRESS_DATA, "", "", G_VARIANT_TYPE("aa{sv}"), NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_ADDRESSES] = g_param_spec_variant(NM_IP6_CONFIG_ADDRESSES, "", "", G_VARIANT_TYPE("a(ayuay)"), NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_ROUTE_DATA] = g_param_spec_variant(NM_IP6_CONFIG_ROUTE_DATA, "", "", G_VARIANT_TYPE("aa{sv}"), NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_ROUTES] = g_param_spec_variant(NM_IP6_CONFIG_ROUTES, "", "", G_VARIANT_TYPE("a(ayuayu)"), NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_GATEWAY] = g_param_spec_string(NM_IP6_CONFIG_GATEWAY, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_NAMESERVERS] = g_param_spec_variant(NM_IP6_CONFIG_NAMESERVERS, "", "", G_VARIANT_TYPE("aay"), NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_DOMAINS] = g_param_spec_boxed(NM_IP6_CONFIG_DOMAINS, "", "", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_SEARCHES] = g_param_spec_boxed(NM_IP6_CONFIG_SEARCHES, "", "", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_DNS_OPTIONS] = g_param_spec_boxed(NM_IP6_CONFIG_DNS_OPTIONS, "", "", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_DNS_PRIORITY] = g_param_spec_int(NM_IP6_CONFIG_DNS_PRIORITY, "", "", G_MININT32, G_MAXINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); }