diff options
| author | Michael Biebl <biebl@debian.org> | 2021-02-11 18:11:46 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-02-11 18:11:46 +0100 |
| commit | 80ec1decc49c72efec2a8b87c06245c92c0ab807 (patch) | |
| tree | e3b229aa94e8dcf0590f2317664176e7b8f7607b /src/core/nm-ip6-config.c | |
| parent | 65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (diff) | |
New upstream version 1.29.90 upstream/1.29.90
Diffstat (limited to 'src/core/nm-ip6-config.c')
| -rw-r--r-- | src/core/nm-ip6-config.c | 2750 |
1 files changed, 2750 insertions, 0 deletions
diff --git a/src/core/nm-ip6-config.c b/src/core/nm-ip6-config.c new file mode 100644 index 00000000..1f7def34 --- /dev/null +++ b/src/core/nm-ip6-config.c @@ -0,0 +1,2750 @@ +/* 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 <arpa/inet.h> +#include <resolv.h> +#include <linux/rtnetlink.h> +#include <linux/if.h> + +#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); +} |