/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2005 - 2017 Red Hat, Inc.
* Copyright (C) 2006 - 2008 Novell, Inc.
*/
#include "nm-default.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);
}