// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2005 - 2017 Red Hat, Inc.
* Copyright (C) 2006 - 2008 Novell, Inc.
*/
#include "nm-default.h"
#include "nm-ip4-config.h"
#include <arpa/inet.h>
#include <resolv.h>
#include <linux/rtnetlink.h>
#include "nm-glib-aux/nm-dedup-multi.h"
#include "nm-utils.h"
#include "platform/nmp-object.h"
#include "platform/nm-platform.h"
#include "platform/nm-platform-utils.h"
#include "NetworkManagerUtils.h"
#include "nm-core-internal.h"
#include "nm-dbus-object.h"
/*****************************************************************************/
/* internal guint32 are assigned to gobject properties of type uint. Ensure, that uint is large enough */
G_STATIC_ASSERT (sizeof (uint) >= sizeof (guint32));
G_STATIC_ASSERT (G_MAXUINT >= 0xFFFFFFFF);
/*****************************************************************************/
static gboolean
_route_valid (const NMPlatformIP4Route *r)
{
return r
&& r->plen <= 32
&& r->network == nm_utils_ip4_address_clear_host_address (r->network, r->plen);
}
/*****************************************************************************/
static void
_idx_obj_id_hash_update (const NMDedupMultiIdxType *idx_type,
const NMDedupMultiObj *obj,
NMHashState *h)
{
nmp_object_id_hash_update ((NMPObject *) obj, h);
}
static gboolean
_idx_obj_id_equal (const NMDedupMultiIdxType *idx_type,
const NMDedupMultiObj *obj_a,
const NMDedupMultiObj *obj_b)
{
return nmp_object_id_equal ((NMPObject *) obj_a, (NMPObject *) obj_b);
}
void
nm_ip_config_dedup_multi_idx_type_init (NMIPConfigDedupMultiIdxType *idx_type,
NMPObjectType obj_type)
{
static const NMDedupMultiIdxTypeClass idx_type_class = {
.idx_obj_id_hash_update = _idx_obj_id_hash_update,
.idx_obj_id_equal = _idx_obj_id_equal,
};
nm_dedup_multi_idx_type_init ((NMDedupMultiIdxType *) idx_type,
&idx_type_class);
idx_type->obj_type = obj_type;
}
/*****************************************************************************/
gboolean
_nm_ip_config_add_obj (NMDedupMultiIndex *multi_idx,
NMIPConfigDedupMultiIdxType *idx_type,
int ifindex,
const NMPObject *obj_new,
const NMPlatformObject *pl_new,
gboolean merge,
gboolean append_force,
const NMPObject **out_obj_old /* returns a reference! */,
const NMPObject **out_obj_new /* does not return a reference */)
{
NMPObject obj_new_stackinit;
const NMDedupMultiEntry *entry_old;
const NMDedupMultiEntry *entry_new;
nm_assert (multi_idx);
nm_assert (idx_type);
nm_assert (NM_IN_SET (idx_type->obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS,
NMP_OBJECT_TYPE_IP4_ROUTE,
NMP_OBJECT_TYPE_IP6_ADDRESS,
NMP_OBJECT_TYPE_IP6_ROUTE));
nm_assert (ifindex > 0);
/* we go through extra lengths to accept a full obj_new object. That one,
* can be reused by increasing the ref-count. */
if (!obj_new) {
nm_assert (pl_new);
obj_new = nmp_object_stackinit (&obj_new_stackinit, idx_type->obj_type, pl_new);
NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (&obj_new_stackinit)->ifindex = ifindex;
} else {
nm_assert (!pl_new);
nm_assert (NMP_OBJECT_GET_TYPE (obj_new) == idx_type->obj_type);
if (NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (obj_new)->ifindex != ifindex) {
obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (&obj_new_stackinit)->ifindex = ifindex;
}
}
nm_assert (NMP_OBJECT_GET_TYPE (obj_new) == idx_type->obj_type);
nm_assert (nmp_object_is_alive (obj_new));
entry_old = nm_dedup_multi_index_lookup_obj (multi_idx, &idx_type->parent, obj_new);
if (entry_old) {
gboolean modified = FALSE;
const NMPObject *obj_old = entry_old->obj;
if (nmp_object_equal (obj_new, obj_old)) {
nm_dedup_multi_entry_set_dirty (entry_old, FALSE);
goto append_force_and_out;
}
/* if @merge, we merge the new object with the existing one.
* Otherwise, we replace it entirely. */
if (merge) {
switch (idx_type->obj_type) {
case NMP_OBJECT_TYPE_IP4_ADDRESS:
case NMP_OBJECT_TYPE_IP6_ADDRESS:
/* for addresses that we read from the kernel, we keep the timestamps as defined
* by the previous source (item_old). The reason is, that the other source configured the lifetimes
* with "what should be" and the kernel values are "what turned out after configuring it".
*
* For other sources, the longer lifetime wins. */
if ( ( obj_new->ip_address.addr_source == NM_IP_CONFIG_SOURCE_KERNEL
&& obj_old->ip_address.addr_source != NM_IP_CONFIG_SOURCE_KERNEL)
|| nm_platform_ip_address_cmp_expiry (NMP_OBJECT_CAST_IP_ADDRESS (obj_old), NMP_OBJECT_CAST_IP_ADDRESS(obj_new)) > 0) {
obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
obj_new_stackinit.ip_address.timestamp = NMP_OBJECT_CAST_IP_ADDRESS (obj_old)->timestamp;
obj_new_stackinit.ip_address.lifetime = NMP_OBJECT_CAST_IP_ADDRESS (obj_old)->lifetime;
obj_new_stackinit.ip_address.preferred = NMP_OBJECT_CAST_IP_ADDRESS (obj_old)->preferred;
modified = TRUE;
}
/* keep the maximum addr_source. */
if (obj_new->ip_address.addr_source < obj_old->ip_address.addr_source) {
obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
obj_new_stackinit.ip_address.addr_source = obj_old->ip_address.addr_source;
modified = TRUE;
}
break;
case NMP_OBJECT_TYPE_IP4_ROUTE:
case NMP_OBJECT_TYPE_IP6_ROUTE:
/* keep the maximum rt_source. */
if (obj_new->ip_route.rt_source < obj_old->ip_route.rt_source) {
obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
obj_new_stackinit.ip_route.rt_source = obj_old->ip_route.rt_source;
modified = TRUE;
}
break;
default:
nm_assert_not_reached ();
break;
}
if ( modified
&& nmp_object_equal (obj_new, obj_old)) {
nm_dedup_multi_entry_set_dirty (entry_old, FALSE);
goto append_force_and_out;
}
}
}
if (!nm_dedup_multi_index_add_full (multi_idx,
&idx_type->parent,
obj_new,
append_force
? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE
: NM_DEDUP_MULTI_IDX_MODE_APPEND,
NULL,
entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING,
NULL,
&entry_new,
out_obj_old)) {
nm_assert_not_reached ();
NM_SET_OUT (out_obj_new, NULL);
return FALSE;
}
NM_SET_OUT (out_obj_new, entry_new->obj);
return TRUE;
append_force_and_out:
NM_SET_OUT (out_obj_old, nmp_object_ref (entry_old->obj));
NM_SET_OUT (out_obj_new, entry_old->obj);
if (append_force) {
if (nm_dedup_multi_entry_reorder (entry_old, NULL, TRUE))
return TRUE;
}
return FALSE;
}
/**
* _nm_ip_config_lookup_ip_route:
* @multi_idx:
* @idx_type:
* @needle:
* @cmp_type: after lookup, filter the result by comparing with @cmp_type. Only
* return the result, if it compares equal to @needle according to this @cmp_type.
* Note that the index uses %NM_PLATFORM_IP_ROUTE_CMP_TYPE_DST type, so passing
* that compare-type means not to filter any further.
*
* Returns: the found entry or %NULL.
*/
const NMDedupMultiEntry *
_nm_ip_config_lookup_ip_route (const NMDedupMultiIndex *multi_idx,
const NMIPConfigDedupMultiIdxType *idx_type,
const NMPObject *needle,
NMPlatformIPRouteCmpType cmp_type)
{
const NMDedupMultiEntry *entry;
nm_assert (multi_idx);
nm_assert (idx_type);
nm_assert (NM_IN_SET (idx_type->obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
nm_assert (NMP_OBJECT_GET_TYPE (needle) == idx_type->obj_type);
entry = nm_dedup_multi_index_lookup_obj (multi_idx,
&idx_type->parent,
needle);
if (!entry)
return NULL;
if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
nm_assert ( ( NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP4_ROUTE
&& nm_platform_ip4_route_cmp (NMP_OBJECT_CAST_IP4_ROUTE (entry->obj), NMP_OBJECT_CAST_IP4_ROUTE (needle), cmp_type) == 0)
|| ( NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP6_ROUTE
&& nm_platform_ip6_route_cmp (NMP_OBJECT_CAST_IP6_ROUTE (entry->obj), NMP_OBJECT_CAST_IP6_ROUTE (needle), cmp_type) == 0));
} else {
if (NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP4_ROUTE) {
if (nm_platform_ip4_route_cmp (NMP_OBJECT_CAST_IP4_ROUTE (entry->obj),
NMP_OBJECT_CAST_IP4_ROUTE (needle),
cmp_type) != 0)
return NULL;
} else {
if (nm_platform_ip6_route_cmp (NMP_OBJECT_CAST_IP6_ROUTE (entry->obj),
NMP_OBJECT_CAST_IP6_ROUTE (needle),
cmp_type) != 0)
return NULL;
}
}
return entry;
}
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE (NMIP4Config,
PROP_MULTI_IDX,
PROP_IFINDEX,
PROP_ADDRESS_DATA,
PROP_ADDRESSES,
PROP_ROUTE_DATA,
PROP_ROUTES,
PROP_GATEWAY,
PROP_NAMESERVER_DATA,
PROP_NAMESERVERS,
PROP_DOMAINS,
PROP_SEARCHES,
PROP_DNS_OPTIONS,
PROP_WINS_SERVER_DATA,
PROP_WINS_SERVERS,
PROP_DNS_PRIORITY,
);
typedef struct {
bool metered:1;
guint32 mtu;
int ifindex;
NMIPConfigSource mtu_source;
int dns_priority;
NMSettingConnectionMdns mdns;
NMSettingConnectionLlmnr llmnr;
GArray *nameservers;
GPtrArray *domains;
GPtrArray *searches;
GPtrArray *dns_options;
GArray *nis;
char *nis_domain;
GArray *wins;
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_ip4_addresses_;
NMDedupMultiIdxType idx_ip4_addresses;
};
union {
NMIPConfigDedupMultiIdxType idx_ip4_routes_;
NMDedupMultiIdxType idx_ip4_routes;
};
NMIPConfigFlags config_flags;
} NMIP4ConfigPrivate;
struct _NMIP4Config {
NMDBusObject parent;
NMIP4ConfigPrivate _priv;
};
struct _NMIP4ConfigClass {
NMDBusObjectClass parent;
};
G_DEFINE_TYPE (NMIP4Config, nm_ip4_config, NM_TYPE_DBUS_OBJECT)
#define NM_IP4_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMIP4Config, NM_IS_IP4_CONFIG)
/*****************************************************************************/
static void _add_address (NMIP4Config *self, const NMPObject *obj_new, const NMPlatformIP4Address *new);
static void _add_route (NMIP4Config *self, const NMPObject *obj_new, const NMPlatformIP4Route *new, const NMPObject **out_obj_new);
static const NMDedupMultiEntry *_lookup_route (const NMIP4Config *self,
const NMPObject *needle,
NMPlatformIPRouteCmpType cmp_type);
/*****************************************************************************/
int
nm_ip4_config_get_ifindex (const NMIP4Config *self)
{
return NM_IP4_CONFIG_GET_PRIVATE (self)->ifindex;
}
NMDedupMultiIndex *
nm_ip4_config_get_multi_idx (const NMIP4Config *self)
{
return NM_IP4_CONFIG_GET_PRIVATE (self)->multi_idx;
}
/*****************************************************************************/
static gboolean
_ipv4_is_zeronet (in_addr_t network)
{
/* Same as ipv4_is_zeronet() from kernel's include/linux/in.h. */
return (network & htonl(0xff000000)) == htonl(0x00000000);
}
/*****************************************************************************/
const NMDedupMultiHeadEntry *
nm_ip4_config_lookup_addresses (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return nm_dedup_multi_index_lookup_head (priv->multi_idx,
&priv->idx_ip4_addresses,
NULL);
}
void
nm_ip_config_iter_ip4_address_init (NMDedupMultiIter *ipconf_iter, const NMIP4Config *self)
{
g_return_if_fail (NM_IS_IP4_CONFIG (self));
nm_dedup_multi_iter_init (ipconf_iter, nm_ip4_config_lookup_addresses (self));
}
/*****************************************************************************/
const NMDedupMultiHeadEntry *
nm_ip4_config_lookup_routes (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return nm_dedup_multi_index_lookup_head (priv->multi_idx,
&priv->idx_ip4_routes,
NULL);
}
void
nm_ip_config_iter_ip4_route_init (NMDedupMultiIter *ipconf_iter, const NMIP4Config *self)
{
g_return_if_fail (NM_IS_IP4_CONFIG (self));
nm_dedup_multi_iter_init (ipconf_iter, nm_ip4_config_lookup_routes (self));
}
/*****************************************************************************/
const NMPObject *
_nm_ip_config_best_default_route_find_better (const NMPObject *obj_cur, const NMPObject *obj_cmp)
{
nm_assert ( !obj_cur
|| NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_cur), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
nm_assert ( !obj_cmp
|| ( !obj_cur
&& NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_cmp), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE))
|| NMP_OBJECT_GET_TYPE (obj_cur) == NMP_OBJECT_GET_TYPE (obj_cmp));
nm_assert ( !obj_cur
|| nm_ip_config_best_default_route_is (obj_cur));
/* assumes that @obj_cur is already the best default route (or NULL). It checks whether
* @obj_cmp is also a default route and returns the best of both. */
if ( obj_cmp
&& nm_ip_config_best_default_route_is (obj_cmp)) {
guint32 metric_cur, metric_cmp;
if (!obj_cur)
return obj_cmp;
metric_cur = NMP_OBJECT_CAST_IP_ROUTE (obj_cur)->metric;
metric_cmp = NMP_OBJECT_CAST_IP_ROUTE (obj_cmp)->metric;
if (metric_cmp < metric_cur)
return obj_cmp;
if (metric_cmp == metric_cur) {
int c;
/* Routes have the same metric. We still want to deterministically
* prefer one or the other. It's important to consistently choose one
* or the other, so that the order doesn't matter how routes are added
* (and merged). */
c = nmp_object_cmp (obj_cur, obj_cmp);
if (c != 0)
return c < 0 ? obj_cur : obj_cmp;
/* as last resort, compare pointers. */
if (obj_cmp < obj_cur)
return obj_cmp;
}
}
return obj_cur;
}
gboolean
_nm_ip_config_best_default_route_set (const NMPObject **best_default_route, const NMPObject *new_candidate)
{
if (new_candidate == *best_default_route)
return FALSE;
nmp_object_ref (new_candidate);
nm_clear_nmp_object (best_default_route);
*best_default_route = new_candidate;
return TRUE;
}
gboolean
_nm_ip_config_best_default_route_merge (const NMPObject **best_default_route, const NMPObject *new_candidate)
{
new_candidate = _nm_ip_config_best_default_route_find_better (*best_default_route,
new_candidate);
return _nm_ip_config_best_default_route_set (best_default_route, new_candidate);
}
const NMPObject *
nm_ip4_config_best_default_route_get (const NMIP4Config *self)
{
g_return_val_if_fail (NM_IS_IP4_CONFIG (self), NULL);
return NM_IP4_CONFIG_GET_PRIVATE (self)->best_default_route;
}
const NMPObject *
_nm_ip4_config_best_default_route_find (const NMIP4Config *self)
{
NMDedupMultiIter ipconf_iter;
const NMPObject *new_best_default_route = NULL;
nm_ip_config_iter_ip4_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;
}
in_addr_t
nmtst_ip4_config_get_gateway (NMIP4Config *config)
{
const NMPObject *rt;
g_assert (NM_IS_IP4_CONFIG (config));
rt = nm_ip4_config_best_default_route_get (config);
if (!rt)
return 0;
return NMP_OBJECT_CAST_IP4_ROUTE (rt)->gateway;
}
/*****************************************************************************/
static void
_notify_addresses (NMIP4Config *self)
{
NMIP4ConfigPrivate *priv = NM_IP4_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 (NMIP4Config *self)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
nm_assert (priv->best_default_route == _nm_ip4_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
_addresses_sort_cmp_get_prio (in_addr_t addr)
{
if (nm_utils_ip4_address_is_link_local (addr))
return 0;
return 1;
}
static int
_addresses_sort_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
{
int p1, p2;
const NMPlatformIP4Address *a1 = NMP_OBJECT_CAST_IP4_ADDRESS (*((const NMPObject **) a));
const NMPlatformIP4Address *a2 = NMP_OBJECT_CAST_IP4_ADDRESS (*((const NMPObject **) b));
guint32 n1, n2;
nm_assert (a1);
nm_assert (a2);
/* Sort by address type. For example link local will
* be sorted *after* a global address. */
p1 = _addresses_sort_cmp_get_prio (a1->address);
p2 = _addresses_sort_cmp_get_prio (a2->address);
if (p1 != p2)
return p1 > p2 ? -1 : 1;
/* Sort the addresses based on their source. */
if (a1->addr_source != a2->addr_source)
return a1->addr_source > a2->addr_source ? -1 : 1;
if ((a1->label[0] == '\0') != (a2->label[0] == '\0'))
return (a1->label[0] == '\0') ? -1 : 1;
/* Finally, sort addresses lexically. We compare only the
* network part so that the order of addresses in the same
* subnet (and thus also the primary/secondary role) is
* preserved.
*/
n1 = a1->address & _nm_utils_ip4_prefix_to_netmask (a1->plen);
n2 = a2->address & _nm_utils_ip4_prefix_to_netmask (a2->plen);
return memcmp (&n1, &n2, sizeof (guint32));
}
/*****************************************************************************/
static int
sort_captured_addresses (const CList *lst_a, const CList *lst_b, gconstpointer user_data)
{
const NMPlatformIP4Address *addr_a = NMP_OBJECT_CAST_IP4_ADDRESS (c_list_entry (lst_a, NMDedupMultiEntry, lst_entries)->obj);
const NMPlatformIP4Address *addr_b = NMP_OBJECT_CAST_IP4_ADDRESS (c_list_entry (lst_b, NMDedupMultiEntry, lst_entries)->obj);
nm_assert (addr_a);
nm_assert (addr_b);
/* Primary addresses first */
return NM_FLAGS_HAS (addr_a->n_ifa_flags, IFA_F_SECONDARY) -
NM_FLAGS_HAS (addr_b->n_ifa_flags, IFA_F_SECONDARY);
}
NMIP4Config *
nm_ip4_config_clone (const NMIP4Config *self)
{
NMIP4Config *copy;
copy = nm_ip4_config_new (nm_ip4_config_get_multi_idx (self), -1);
nm_ip4_config_replace (copy, self, NULL);
return copy;
}
NMIP4Config *
nm_ip4_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex)
{
NMIP4Config *self;
NMIP4ConfigPrivate *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_ip4_config_new (multi_idx, ifindex);
priv = NM_IP4_CONFIG_GET_PRIVATE (self);
head_entry = nm_platform_lookup_object (platform,
NMP_OBJECT_TYPE_IP4_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_ip4_addresses_,
ifindex,
plobj,
NULL,
FALSE,
TRUE,
NULL,
NULL))
nm_assert_not_reached ();
}
head_entry = nm_ip4_config_lookup_addresses (self);
nm_assert (head_entry);
nm_dedup_multi_head_entry_sort (head_entry,
sort_captured_addresses,
NULL);
_notify_addresses (self);
}
head_entry = nm_platform_lookup_object (platform,
NMP_OBJECT_TYPE_IP4_ROUTE,
ifindex);
/* Extract gateway from default route */
nmp_cache_iter_for_each (&iter, head_entry, &plobj)
_add_route (self, plobj, NULL, NULL);
return self;
}
void
nm_ip4_config_update_routes_metric (NMIP4Config *self, gint64 metric)
{
gs_free NMPlatformIP4Route *routes = NULL;
gboolean need_update = FALSE;
const NMPlatformIP4Route *r;
NMDedupMultiIter iter;
guint num = 0, i = 0;
nm_ip_config_iter_ip4_route_for_each (&iter, self, &r) {
if (r->metric != metric)
need_update = TRUE;
num++;
}
if (!need_update)
return;
routes = g_new (NMPlatformIP4Route, num);
nm_ip_config_iter_ip4_route_for_each (&iter, self, &r) {
routes[i] = *r;
routes[i].metric = metric;
i++;
}
g_object_freeze_notify (G_OBJECT (self));
nm_ip4_config_reset_routes (self);
for (i = 0; i < num; i++)
nm_ip4_config_add_route (self, &routes[i], NULL);
g_object_thaw_notify (G_OBJECT (self));
}
static void
_add_local_route_from_addr4 (NMIP4Config * self,
const NMPlatformIP4Address *addr,
int ifindex,
guint32 route_table,
gboolean is_vrf)
{
nm_auto_nmpobj NMPObject *r = NULL;
NMPlatformIP4Route *route;
r = nmp_object_new (NMP_OBJECT_TYPE_IP4_ROUTE, NULL);
route = NMP_OBJECT_CAST_IP4_ROUTE (r);
route->ifindex = ifindex;
route->rt_source = NM_IP_CONFIG_SOURCE_KERNEL;
route->network = addr->address;
route->plen = 32;
route->pref_src = addr->address;
route->type_coerced = nm_platform_route_type_coerce (RTN_LOCAL);
route->scope_inv = nm_platform_route_scope_inv (RT_SCOPE_HOST);
route->table_coerced = nm_platform_route_table_coerce (is_vrf ? route_table : RT_TABLE_LOCAL);
_add_route (self, r, NULL, NULL);
}
void
nm_ip4_config_add_dependent_routes (NMIP4Config *self,
guint32 route_table,
guint32 route_metric,
gboolean is_vrf,
GPtrArray ** out_ip4_dev_route_blacklist)
{
GPtrArray *ip4_dev_route_blacklist = NULL;
const NMPlatformIP4Address *my_addr;
const NMPlatformIP4Route *my_route;
int ifindex;
NMDedupMultiIter iter;
g_return_if_fail (NM_IS_IP4_CONFIG (self));
ifindex = nm_ip4_config_get_ifindex (self);
g_return_if_fail (ifindex > 0);
/* For IPv6 slaac, we explicitly add the device-routes (onlink) to NMIP6Config.
* As we don't do that for IPv4 (and manual IPv6 addresses), add them explicitly. */
nm_ip_config_iter_ip4_address_for_each (&iter, self, &my_addr) {
nm_auto_nmpobj NMPObject *r = NULL;
NMPlatformIP4Route *route;
in_addr_t network;
if (my_addr->plen == 0)
continue;
nm_assert (my_addr->plen <= 32);
/* The destination network depends on the peer-address. */
network = nm_utils_ip4_address_clear_host_address (my_addr->peer_address, my_addr->plen);
if (my_addr->external)
continue;
_add_local_route_from_addr4 (self, my_addr, ifindex, route_table, is_vrf);
if (_ipv4_is_zeronet (network)) {
/* Kernel doesn't add device-routes for destinations that
* start with 0.x.y.z. Skip them. */
continue;
}
if ( my_addr->plen == 32
&& my_addr->address == my_addr->peer_address) {
/* Kernel doesn't add device-routes for /32 addresses unless
* they have a peer. */
continue;
}
r = nmp_object_new (NMP_OBJECT_TYPE_IP4_ROUTE, NULL);
route = NMP_OBJECT_CAST_IP4_ROUTE (r);
route->ifindex = ifindex;
route->rt_source = NM_IP_CONFIG_SOURCE_KERNEL;
route->network = network;
route->plen = my_addr->plen;
route->pref_src = my_addr->address;
route->table_coerced = nm_platform_route_table_coerce (route_table);
route->metric = route_metric;
route->scope_inv = nm_platform_route_scope_inv (NM_RT_SCOPE_LINK);
nm_platform_ip_route_normalize (AF_INET, (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);
if ( out_ip4_dev_route_blacklist
&& ( route_table != RT_TABLE_MAIN
|| route_metric != NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE)) {
nm_auto_nmpobj NMPObject *r_dev = NULL;
r_dev = nmp_object_clone (r, FALSE);
route = NMP_OBJECT_CAST_IP4_ROUTE (r_dev);
route->table_coerced = nm_platform_route_table_coerce (RT_TABLE_MAIN);
route->metric = NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE;
nm_platform_ip_route_normalize (AF_INET, (NMPlatformIPRoute *) route);
if (_lookup_route (self,
r_dev,
NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID)) {
/* we track such a route explicitly. Don't blacklist it. */
} else {
if (!ip4_dev_route_blacklist)
ip4_dev_route_blacklist = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
g_ptr_array_add (ip4_dev_route_blacklist,
g_steal_pointer (&r_dev));
}
}
}
again:
nm_ip_config_iter_ip4_route_for_each (&iter, self, &my_route) {
NMPlatformIP4Route rt;
if ( !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (my_route)
|| my_route->gateway == 0
|| NM_IS_IP_CONFIG_SOURCE_RTPROT (my_route->rt_source)
|| nm_ip4_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 = 32;
rt.gateway = 0;
_add_route (self, NULL, &rt, NULL);
/* adding the route might have invalidated the iteration. Start again. */
goto again;
}
NM_SET_OUT (out_ip4_dev_route_blacklist, ip4_dev_route_blacklist);
}
gboolean
nm_ip4_config_commit (const NMIP4Config *self,
NMPlatform *platform,
NMIPRouteTableSyncMode route_table_sync)
{
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_IP4_CONFIG (self), FALSE);
ifindex = nm_ip4_config_get_ifindex (self);
g_return_val_if_fail (ifindex > 0, FALSE);
addresses = nm_dedup_multi_objs_to_ptr_array_head (nm_ip4_config_lookup_addresses (self),
NULL, NULL);
routes = nm_dedup_multi_objs_to_ptr_array_head (nm_ip4_config_lookup_routes (self),
NULL, NULL);
routes_prune = nm_platform_ip_route_get_prune_list (platform,
AF_INET,
ifindex,
route_table_sync);
nm_platform_ip4_address_sync (platform, ifindex, addresses);
if (!nm_platform_ip_route_sync (platform,
AF_INET,
ifindex,
routes,
routes_prune,
NULL))
success = FALSE;
return success;
}
void
_nm_ip_config_merge_route_attributes (int addr_family,
NMIPRoute *s_route,
NMPlatformIPRoute *r,
guint32 route_table)
{
GVariant *variant;
guint32 table;
NMIPAddr addr;
NMPlatformIP4Route *r4 = (NMPlatformIP4Route *) r;
NMPlatformIP6Route *r6 = (NMPlatformIP6Route *) r;
gboolean onlink;
nm_assert (s_route);
nm_assert_addr_family (addr_family);
nm_assert (r);
#define GET_ATTR(name, dst, variant_type, type, dflt) \
G_STMT_START { \
GVariant *_variant = nm_ip_route_get_attribute (s_route, ""name""); \
\
if ( _variant \
&& g_variant_is_of_type (_variant, G_VARIANT_TYPE_ ## variant_type)) \
(dst) = g_variant_get_ ## type (_variant); \
else \
(dst) = (dflt); \
} G_STMT_END
if ( (variant = nm_ip_route_get_attribute (s_route, NM_IP_ROUTE_ATTRIBUTE_TYPE))
&& g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) {
guint8 type;
type = nm_utils_route_type_by_name (g_variant_get_string (variant, NULL));
nm_assert (NM_IN_SET (type,
RTN_UNICAST,
RTN_LOCAL));
r->type_coerced = nm_platform_route_type_coerce (type);
} else
r->type_coerced = nm_platform_route_type_coerce (RTN_UNICAST);
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_TABLE, table, UINT32, uint32, 0);
if ( !table
&& r->type_coerced == nm_platform_route_type_coerce (RTN_LOCAL))
r->table_coerced = nm_platform_route_table_coerce (RT_TABLE_LOCAL);
else
r->table_coerced = nm_platform_route_table_coerce (table ?: (route_table ?: RT_TABLE_MAIN));
if (addr_family == AF_INET) {
guint8 scope;
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_TOS, r4->tos, BYTE, byte, 0);
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_SCOPE, scope, BYTE, byte, RT_SCOPE_NOWHERE);
r4->scope_inv = nm_platform_route_scope_inv (scope);
}
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_ONLINK, onlink, BOOLEAN, boolean, FALSE);
r->r_rtm_flags = ((onlink) ? (unsigned) RTNH_F_ONLINK : 0u);
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_WINDOW, r->window, UINT32, uint32, 0);
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_CWND, r->cwnd, UINT32, uint32, 0);
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_INITCWND, r->initcwnd, UINT32, uint32, 0);
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_INITRWND, r->initrwnd, UINT32, uint32, 0);
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_MTU, r->mtu, UINT32, uint32, 0);
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_LOCK_WINDOW, r->lock_window, BOOLEAN, boolean, FALSE);
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, r->lock_cwnd, BOOLEAN, boolean, FALSE);
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_LOCK_INITCWND, r->lock_initcwnd, BOOLEAN, boolean, FALSE);
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_LOCK_INITRWND, r->lock_initrwnd, BOOLEAN, boolean, FALSE);
GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, r->lock_mtu, BOOLEAN, boolean, FALSE);
if ( (variant = nm_ip_route_get_attribute (s_route, NM_IP_ROUTE_ATTRIBUTE_SRC))
&& g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) {
if (inet_pton (addr_family, g_variant_get_string (variant, NULL), &addr) == 1) {
if (addr_family == AF_INET)
r4->pref_src = addr.addr4;
else
r6->pref_src = addr.addr6;
}
}
if ( addr_family == AF_INET6
&& (variant = nm_ip_route_get_attribute (s_route, NM_IP_ROUTE_ATTRIBUTE_FROM))
&& g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) {
gs_free char *string = NULL;
guint8 plen = 128;
char *sep;
string = g_variant_dup_string (variant, NULL);
sep = strchr (string, '/');
if (sep) {
*sep = 0;
plen = _nm_utils_ascii_str_to_int64 (sep + 1, 10, 1, 128, 255);
}
if ( plen <= 128
&& inet_pton (AF_INET6, string, &addr) == 1) {
r6->src = addr.addr6;
r6->src_plen = plen;
}
}
#undef GET_ATTR
}
void
nm_ip4_config_merge_setting (NMIP4Config *self,
NMSettingIPConfig *setting,
NMSettingConnectionMdns mdns,
NMSettingConnectionLlmnr llmnr,
guint32 route_table,
guint32 route_metric)
{
guint naddresses, nroutes, nnameservers, nsearches;
int i, priority;
const char *gateway_str;
guint32 gateway_bin;
if (!setting)
return;
g_return_if_fail (NM_IS_SETTING_IP4_CONFIG (setting));
g_object_freeze_notify (G_OBJECT (self));
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);
/* Gateway */
if ( !nm_setting_ip_config_get_never_default (setting)
&& (gateway_str = nm_setting_ip_config_get_gateway (setting))
&& inet_pton (AF_INET, gateway_str, &gateway_bin) == 1
&& gateway_bin) {
const NMPlatformIP4Route 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);
GVariant *label;
NMPlatformIP4Address address;
memset (&address, 0, sizeof (address));
nm_ip_address_get_address_binary (s_addr, &address.address);
address.peer_address = address.address;
address.plen = nm_ip_address_get_prefix (s_addr);
nm_assert (address.plen <= 32);
address.lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
address.preferred = NM_PLATFORM_LIFETIME_PERMANENT;
address.addr_source = NM_IP_CONFIG_SOURCE_USER;
label = nm_ip_address_get_attribute (s_addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL);
if (label)
g_strlcpy (address.label, g_variant_get_string (label, NULL), sizeof (address.label));
_add_address (self, NULL, &address);
}
/* Routes */
for (i = 0; i < nroutes; i++) {
NMIPRoute *s_route = nm_setting_ip_config_get_route (setting, i);
NMPlatformIP4Route route;
gint64 m;
if (nm_ip_route_get_family (s_route) != AF_INET) {
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 <= 32);
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 = m;
route.rt_source = NM_IP_CONFIG_SOURCE_USER;
route.network = nm_utils_ip4_address_clear_host_address (route.network, route.plen);
_nm_ip_config_merge_route_attributes (AF_INET,
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_ip4_config_reset_nameservers (self);
nm_ip4_config_reset_domains (self);
nm_ip4_config_reset_searches (self);
}
for (i = 0; i < nnameservers; i++) {
guint32 ip;
if (inet_pton (AF_INET, nm_setting_ip_config_get_dns (setting, i), &ip) == 1)
nm_ip4_config_add_nameserver (self, ip);
}
for (i = 0; i < nsearches; i++)
nm_ip4_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_ip4_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_ip4_config_set_dns_priority (self, priority);
nm_ip4_config_mdns_set (self, mdns);
nm_ip4_config_llmnr_set (self, llmnr);
g_object_thaw_notify (G_OBJECT (self));
}
NMSetting *
nm_ip4_config_create_setting (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv;
NMSettingIPConfig *s_ip4;
guint nnameservers, nsearches, noptions;
const char *method = NULL;
int i;
NMDedupMultiIter ipconf_iter;
const NMPlatformIP4Address *address;
const NMPlatformIP4Route *route;
char sbuf[NM_UTILS_INET_ADDRSTRLEN];
s_ip4 = NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new ());
if (!self) {
g_object_set (s_ip4,
NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
NULL);
return NM_SETTING (s_ip4);
}
priv = NM_IP4_CONFIG_GET_PRIVATE (self);
nnameservers = nm_ip4_config_get_num_nameservers (self);
nsearches = nm_ip4_config_get_num_searches (self);
noptions = nm_ip4_config_get_num_dns_options (self);
/* Addresses */
nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, self, &address) {
NMIPAddress *s_addr;
/* Detect dynamic address */
if (address->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) {
method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
continue;
}
/* Static address found. */
if (!method)
method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL;
s_addr = nm_ip_address_new_binary (AF_INET, &address->address, address->plen, NULL);
if (*address->label)
nm_ip_address_set_attribute (s_addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL, g_variant_new_string (address->label));
nm_setting_ip_config_add_address (s_ip4, s_addr);
nm_ip_address_unref (s_addr);
}
/* Gateway */
if ( priv->best_default_route
&& nm_setting_ip_config_get_num_addresses (s_ip4) > 0) {
g_object_set (s_ip4,
NM_SETTING_IP_CONFIG_GATEWAY,
_nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway,
sbuf),
NULL);
}
/* Use 'disabled' if the method wasn't previously set */
if (!method)
method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
g_object_set (s_ip4,
NM_SETTING_IP_CONFIG_METHOD, method,
NULL);
/* Routes */
nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route) {
NMIPRoute *s_route;
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_INET,
&route->network,
route->plen,
&route->gateway,
route->metric,
NULL);
nm_setting_ip_config_add_route (s_ip4, s_route);
nm_ip_route_unref (s_route);
}
/* DNS */
for (i = 0; i < nnameservers; i++) {
guint32 nameserver = nm_ip4_config_get_nameserver (self, i);
nm_setting_ip_config_add_dns (s_ip4, _nm_utils_inet4_ntop (nameserver, sbuf));
}
for (i = 0; i < nsearches; i++) {
const char *search = nm_ip4_config_get_search (self, i);
nm_setting_ip_config_add_dns_search (s_ip4, search);
}
for (i = 0; i < noptions; i++) {
const char *option = nm_ip4_config_get_dns_option (self, i);
nm_setting_ip_config_add_dns_option (s_ip4, option);
}
g_object_set (s_ip4,
NM_SETTING_IP_CONFIG_DNS_PRIORITY,
nm_ip4_config_get_dns_priority (self),
NULL);
return NM_SETTING (s_ip4);
}
/*****************************************************************************/
void
nm_ip4_config_merge (NMIP4Config *dst,
const NMIP4Config *src,
NMIPConfigMergeFlags merge_flags,
guint32 default_route_metric_penalty)
{
NMIP4ConfigPrivate *dst_priv;
const NMIP4ConfigPrivate *src_priv;
guint32 i;
NMDedupMultiIter ipconf_iter;
const NMPlatformIP4Address *address = NULL;
g_return_if_fail (src != NULL);
g_return_if_fail (dst != NULL);
dst_priv = NM_IP4_CONFIG_GET_PRIVATE (dst);
src_priv = NM_IP4_CONFIG_GET_PRIVATE (src);
g_object_freeze_notify (G_OBJECT (dst));
/* addresses */
nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, src, &address) {
if ( NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_EXTERNAL)
&& !address->external) {
NMPlatformIP4Address 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_ip4_config_get_num_nameservers (src); i++)
nm_ip4_config_add_nameserver (dst, nm_ip4_config_get_nameserver (src, i));
}
/* routes */
if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_ROUTES)) {
const NMPlatformIP4Route *r_src;
nm_ip_config_iter_ip4_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) {
NMPlatformIP4Route 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_ip4_config_get_num_domains (src); i++)
nm_ip4_config_add_domain (dst, nm_ip4_config_get_domain (src, i));
}
/* dns searches */
if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) {
for (i = 0; i < nm_ip4_config_get_num_searches (src); i++)
nm_ip4_config_add_search (dst, nm_ip4_config_get_search (src, i));
}
/* dns options */
if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) {
for (i = 0; i < nm_ip4_config_get_num_dns_options (src); i++)
nm_ip4_config_add_dns_option (dst, nm_ip4_config_get_dns_option (src, i));
}
/* MTU */
if ( src_priv->mtu_source > dst_priv->mtu_source
|| ( src_priv->mtu_source == dst_priv->mtu_source
&& ( (!dst_priv->mtu && src_priv->mtu)
|| (dst_priv->mtu && src_priv->mtu < dst_priv->mtu))))
nm_ip4_config_set_mtu (dst, src_priv->mtu, src_priv->mtu_source);
/* NIS */
if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) {
for (i = 0; i < nm_ip4_config_get_num_nis_servers (src); i++)
nm_ip4_config_add_nis_server (dst, nm_ip4_config_get_nis_server (src, i));
if (nm_ip4_config_get_nis_domain (src))
nm_ip4_config_set_nis_domain (dst, nm_ip4_config_get_nis_domain (src));
}
/* WINS */
if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) {
for (i = 0; i < nm_ip4_config_get_num_wins (src); i++)
nm_ip4_config_add_wins (dst, nm_ip4_config_get_wins (src, i));
}
/* metered flag */
nm_ip4_config_set_metered (dst, nm_ip4_config_get_metered (dst) ||
nm_ip4_config_get_metered (src));
/* DNS priority */
if (nm_ip4_config_get_dns_priority (src))
nm_ip4_config_set_dns_priority (dst, nm_ip4_config_get_dns_priority (src));
/* mdns */
nm_ip4_config_mdns_set (dst,
NM_MAX (nm_ip4_config_mdns_get (src),
nm_ip4_config_mdns_get (dst)));
/* LLMNR */
nm_ip4_config_llmnr_set (dst,
NM_MAX (nm_ip4_config_llmnr_get (src),
nm_ip4_config_llmnr_get (dst)));
g_object_thaw_notify (G_OBJECT (dst));
}
/*****************************************************************************/
static int
_nameservers_get_index (const NMIP4Config *self, guint32 ns)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
guint i;
for (i = 0; i < priv->nameservers->len; i++) {
guint32 n = g_array_index (priv->nameservers, guint32, i);
if (ns == n)
return (int) i;
}
return -1;
}
static int
_domains_get_index (const NMIP4Config *self, const char *domain)
{
const NMIP4ConfigPrivate *priv = NM_IP4_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 NMIP4Config *self, const char *search)
{
const NMIP4ConfigPrivate *priv = NM_IP4_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 NMIP4Config *self, const char *option)
{
const NMIP4ConfigPrivate *priv = NM_IP4_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;
}
static int
_nis_servers_get_index (const NMIP4Config *self, guint32 nis_server)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
guint i;
for (i = 0; i < priv->nis->len; i++) {
guint32 n = g_array_index (priv->nis, guint32, i);
if (n == nis_server)
return (int) i;
}
return -1;
}
static int
_wins_get_index (const NMIP4Config *self, guint32 wins_server)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
guint i;
for (i = 0; i < priv->wins->len; i++) {
guint32 n = g_array_index (priv->wins, guint32, i);
if (n == wins_server)
return (int) i;
}
return -1;
}
/*****************************************************************************/
/**
* nm_ip4_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_ip4_config_subtract (NMIP4Config *dst,
const NMIP4Config *src,
guint32 default_route_metric_penalty)
{
NMIP4ConfigPrivate *dst_priv;
guint i;
int idx;
const NMPlatformIP4Address *a;
const NMPlatformIP4Route *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_IP4_CONFIG_GET_PRIVATE (dst);
g_object_freeze_notify (G_OBJECT (dst));
/* addresses */
changed = FALSE;
nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, src, &a) {
if (nm_dedup_multi_index_remove_obj (dst_priv->multi_idx,
&dst_priv->idx_ip4_addresses,
NMP_OBJECT_UP_CAST (a),
NULL))
changed = TRUE;
}
if (changed)
_notify_addresses (dst);
/* nameservers */
for (i = 0; i < nm_ip4_config_get_num_nameservers (src); i++) {
idx = _nameservers_get_index (dst, nm_ip4_config_get_nameserver (src, i));
if (idx >= 0)
nm_ip4_config_del_nameserver (dst, idx);
}
/* routes */
changed = FALSE;
changed_default_route = FALSE;
nm_ip_config_iter_ip4_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) {
NMPlatformIP4Route *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_IP4_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_ip4_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) {
_nm_ip_config_best_default_route_set (&dst_priv->best_default_route,
_nm_ip4_config_best_default_route_find (dst));
_notify (dst, PROP_GATEWAY);
}
if (changed)
_notify_routes (dst);
/* domains */
for (i = 0; i < nm_ip4_config_get_num_domains (src); i++) {
idx = _domains_get_index (dst, nm_ip4_config_get_domain (src, i));
if (idx >= 0)
nm_ip4_config_del_domain (dst, idx);
}
/* dns searches */
for (i = 0; i < nm_ip4_config_get_num_searches (src); i++) {
idx = _searches_get_index (dst, nm_ip4_config_get_search (src, i));
if (idx >= 0)
nm_ip4_config_del_search (dst, idx);
}
/* dns options */
for (i = 0; i < nm_ip4_config_get_num_dns_options (src); i++) {
idx = _dns_options_get_index (dst, nm_ip4_config_get_dns_option (src, i));
if (idx >= 0)
nm_ip4_config_del_dns_option (dst, idx);
}
/* MTU */
if ( nm_ip4_config_get_mtu (src) == nm_ip4_config_get_mtu (dst)
&& nm_ip4_config_get_mtu_source (src) == nm_ip4_config_get_mtu_source (dst))
nm_ip4_config_set_mtu (dst, 0, NM_IP_CONFIG_SOURCE_UNKNOWN);
/* NIS */
for (i = 0; i < nm_ip4_config_get_num_nis_servers (src); i++) {
idx = _nis_servers_get_index (dst, nm_ip4_config_get_nis_server (src, i));
if (idx >= 0)
nm_ip4_config_del_nis_server (dst, idx);
}
if (g_strcmp0 (nm_ip4_config_get_nis_domain (src), nm_ip4_config_get_nis_domain (dst)) == 0)
nm_ip4_config_set_nis_domain (dst, NULL);
/* WINS */
for (i = 0; i < nm_ip4_config_get_num_wins (src); i++) {
idx = _wins_get_index (dst, nm_ip4_config_get_wins (src, i));
if (idx >= 0)
nm_ip4_config_del_wins (dst, idx);
}
/* DNS priority */
if (nm_ip4_config_get_dns_priority (src) == nm_ip4_config_get_dns_priority (dst))
nm_ip4_config_set_dns_priority (dst, 0);
/* mdns */
if (nm_ip4_config_mdns_get (src) == nm_ip4_config_mdns_get (dst))
nm_ip4_config_mdns_set (dst, NM_SETTING_CONNECTION_MDNS_DEFAULT);
/* LLMNR */
if (nm_ip4_config_llmnr_get (src) == nm_ip4_config_llmnr_get (dst))
nm_ip4_config_llmnr_set (dst, NM_SETTING_CONNECTION_LLMNR_DEFAULT);
g_object_thaw_notify (G_OBJECT (dst));
}
static gboolean
_nm_ip4_config_intersect_helper (NMIP4Config *dst,
const NMIP4Config *src,
gboolean intersect_addresses,
gboolean intersect_routes,
guint32 default_route_metric_penalty,
gboolean update_dst)
{
NMIP4ConfigPrivate *dst_priv;
const NMIP4ConfigPrivate *src_priv;
NMDedupMultiIter ipconf_iter;
const NMPlatformIP4Address *a;
const NMPlatformIP4Route *r;
const NMPObject *new_best_default_route;
gboolean changed, result = FALSE;
g_return_val_if_fail (src, FALSE);
g_return_val_if_fail (dst, FALSE);
dst_priv = NM_IP4_CONFIG_GET_PRIVATE (dst);
src_priv = NM_IP4_CONFIG_GET_PRIVATE (src);
if (update_dst)
g_object_freeze_notify (G_OBJECT (dst));
/* addresses */
if (intersect_addresses) {
changed = FALSE;
nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, dst, &a) {
if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
&src_priv->idx_ip4_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_ip4_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) {
NMPlatformIP4Route *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_IP4_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_ip4_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 (_nm_ip_config_best_default_route_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 */
/* ignore NIS */
/* ignore WINS */
/* ignore mdns */
/* ignore LLMNR */
if (update_dst)
g_object_thaw_notify (G_OBJECT (dst));
return result;
}
/**
* nm_ip4_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_ip4_config_intersect (NMIP4Config *dst,
const NMIP4Config *src,
gboolean intersect_addresses,
gboolean intersect_routes,
guint32 default_route_metric_penalty)
{
_nm_ip4_config_intersect_helper (dst,
src,
intersect_addresses,
intersect_routes,
default_route_metric_penalty,
TRUE);
}
/**
* nm_ip4_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.
*/
NMIP4Config *
nm_ip4_config_intersect_alloc (const NMIP4Config *a,
const NMIP4Config *b,
gboolean intersect_addresses,
gboolean intersect_routes,
guint32 default_route_metric_penalty)
{
NMIP4Config *a_copy;
if (_nm_ip4_config_intersect_helper ((NMIP4Config *) a, b,
intersect_addresses,
intersect_routes,
default_route_metric_penalty,
FALSE)) {
a_copy = nm_ip4_config_clone (a);
_nm_ip4_config_intersect_helper (a_copy,
b,
intersect_addresses,
intersect_routes,
default_route_metric_penalty,
TRUE);
return a_copy;
} else
return NULL;
}
/**
* nm_ip4_config_replace:
* @dst: config to replace with @src content
* @src: source config to copy
* @relevant_changes: return whether there are changes to the
* destination object that are relevant. This is equal to
* nm_ip4_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_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *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;
NMIP4ConfigPrivate *dst_priv;
const NMIP4ConfigPrivate *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 (src != NULL, FALSE);
g_return_val_if_fail (dst != NULL, FALSE);
g_return_val_if_fail (src != dst, FALSE);
#if NM_MORE_ASSERTS
config_equal = nm_ip4_config_equal (dst, src);
#endif
dst_priv = NM_IP4_CONFIG_GET_PRIVATE (dst);
src_priv = NM_IP4_CONFIG_GET_PRIVATE (src);
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_ip4_config_lookup_addresses (src);
nm_dedup_multi_iter_init (&ipconf_iter_src, head_entry_src);
nm_ip_config_iter_ip4_address_init (&ipconf_iter_dst, dst);
are_equal = TRUE;
while (TRUE) {
gboolean has;
const NMPlatformIP4Address *r_src = NULL;
const NMPlatformIP4Address *r_dst = NULL;
has = nm_ip_config_iter_ip4_address_next (&ipconf_iter_src, &r_src);
if (has != nm_ip_config_iter_ip4_address_next (&ipconf_iter_dst, &r_dst)) {
are_equal = FALSE;
has_relevant_changes = TRUE;
break;
}
if (!has)
break;
if (nm_platform_ip4_address_cmp (r_src, r_dst) != 0) {
are_equal = FALSE;
if ( r_src->address != r_dst->address
|| r_src->plen != r_dst->plen
|| r_src->peer_address != r_dst->peer_address) {
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_ip4_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_ip4_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_ip4_addresses, FALSE);
_notify_addresses (dst);
}
/* routes */
head_entry_src = nm_ip4_config_lookup_routes (src);
nm_dedup_multi_iter_init (&ipconf_iter_src, head_entry_src);
nm_ip_config_iter_ip4_route_init (&ipconf_iter_dst, dst);
are_equal = TRUE;
while (TRUE) {
gboolean has;
const NMPlatformIP4Route *r_src = NULL;
const NMPlatformIP4Route *r_dst = NULL;
has = nm_ip_config_iter_ip4_route_next (&ipconf_iter_src, &r_src);
if (has != nm_ip_config_iter_ip4_route_next (&ipconf_iter_dst, &r_dst)) {
are_equal = FALSE;
has_relevant_changes = TRUE;
break;
}
if (!has)
break;
if (nm_platform_ip4_route_cmp_full (r_src, r_dst) != 0) {
are_equal = FALSE;
if ( r_src->plen != r_dst->plen
|| !nm_utils_ip4_address_same_prefix (r_src->network, r_dst->network, r_src->plen)
|| r_src->gateway != r_dst->gateway
|| r_src->metric != r_dst->metric) {
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_ip4_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_ip4_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_ip4_routes, FALSE);
if (_nm_ip_config_best_default_route_set (&dst_priv->best_default_route, new_best_default_route))
_notify (dst, PROP_GATEWAY);
_notify_routes (dst);
}
/* nameservers */
num = nm_ip4_config_get_num_nameservers (src);
are_equal = num == nm_ip4_config_get_num_nameservers (dst);
if (are_equal) {
for (i = 0; i < num; i++ ) {
if (nm_ip4_config_get_nameserver (src, i) != nm_ip4_config_get_nameserver (dst, i)) {
are_equal = FALSE;
break;
}
}
}
if (!are_equal) {
nm_ip4_config_reset_nameservers (dst);
for (i = 0; i < num; i++)
nm_ip4_config_add_nameserver (dst, nm_ip4_config_get_nameserver (src, i));
has_relevant_changes = TRUE;
}
/* domains */
num = nm_ip4_config_get_num_domains (src);
are_equal = num == nm_ip4_config_get_num_domains (dst);
if (are_equal) {
for (i = 0; i < num; i++ ) {
if (g_strcmp0 (nm_ip4_config_get_domain (src, i),
nm_ip4_config_get_domain (dst, i))) {
are_equal = FALSE;
break;
}
}
}
if (!are_equal) {
nm_ip4_config_reset_domains (dst);
for (i = 0; i < num; i++)
nm_ip4_config_add_domain (dst, nm_ip4_config_get_domain (src, i));
has_relevant_changes = TRUE;
}
/* dns searches */
num = nm_ip4_config_get_num_searches (src);
are_equal = num == nm_ip4_config_get_num_searches (dst);
if (are_equal) {
for (i = 0; i < num; i++ ) {
if (g_strcmp0 (nm_ip4_config_get_search (src, i),
nm_ip4_config_get_search (dst, i))) {
are_equal = FALSE;
break;
}
}
}
if (!are_equal) {
nm_ip4_config_reset_searches (dst);
for (i = 0; i < num; i++)
nm_ip4_config_add_search (dst, nm_ip4_config_get_search (src, i));
has_relevant_changes = TRUE;
}
/* dns options */
num = nm_ip4_config_get_num_dns_options (src);
are_equal = num == nm_ip4_config_get_num_dns_options (dst);
if (are_equal) {
for (i = 0; i < num; i++ ) {
if (g_strcmp0 (nm_ip4_config_get_dns_option (src, i),
nm_ip4_config_get_dns_option (dst, i))) {
are_equal = FALSE;
break;
}
}
}
if (!are_equal) {
nm_ip4_config_reset_dns_options (dst);
for (i = 0; i < num; i++)
nm_ip4_config_add_dns_option (dst, nm_ip4_config_get_dns_option (src, i));
has_relevant_changes = TRUE;
}
if (src_priv->mdns != dst_priv->mdns) {
dst_priv->mdns = src_priv->mdns;
has_relevant_changes = TRUE;
}
if (src_priv->llmnr != dst_priv->llmnr) {
dst_priv->llmnr = src_priv->llmnr;
has_relevant_changes = TRUE;
}
/* DNS priority */
if (src_priv->dns_priority != dst_priv->dns_priority) {
nm_ip4_config_set_dns_priority (dst, src_priv->dns_priority);
has_minor_changes = TRUE;
}
/* nis */
num = nm_ip4_config_get_num_nis_servers (src);
are_equal = num == nm_ip4_config_get_num_nis_servers (dst);
if (are_equal) {
for (i = 0; i < num; i++ ) {
if (nm_ip4_config_get_nis_server (src, i) != nm_ip4_config_get_nis_server (dst, i)) {
are_equal = FALSE;
break;
}
}
}
if (!are_equal) {
nm_ip4_config_reset_nis_servers (dst);
for (i = 0; i < num; i++)
nm_ip4_config_add_nis_server (dst, nm_ip4_config_get_nis_server (src, i));
has_relevant_changes = TRUE;
}
/* nis_domain */
if (g_strcmp0 (src_priv->nis_domain, dst_priv->nis_domain)) {
nm_ip4_config_set_nis_domain (dst, src_priv->nis_domain);
has_relevant_changes = TRUE;
}
/* wins */
num = nm_ip4_config_get_num_wins (src);
are_equal = num == nm_ip4_config_get_num_wins (dst);
if (are_equal) {
for (i = 0; i < num; i++ ) {
if (nm_ip4_config_get_wins (src, i) != nm_ip4_config_get_wins (dst, i)) {
are_equal = FALSE;
break;
}
}
}
if (!are_equal) {
nm_ip4_config_reset_wins (dst);
for (i = 0; i < num; i++)
nm_ip4_config_add_wins (dst, nm_ip4_config_get_wins (src, i));
has_relevant_changes = TRUE;
}
/* mtu */
if ( src_priv->mtu != dst_priv->mtu
|| src_priv->mtu_source != dst_priv->mtu_source) {
nm_ip4_config_set_mtu (dst, src_priv->mtu, src_priv->mtu_source);
has_minor_changes = TRUE;
}
/* metered */
if (src_priv->metered != dst_priv->metered) {
dst_priv->metered = src_priv->metered;
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_ip_config_dump (const NMIPConfig *self,
const char *detail,
NMLogLevel level,
NMLogDomain domain)
{
NMDedupMultiIter ipconf_iter;
const NMPlatformIP4Address *addr4;
const NMPlatformIP6Address *addr6;
const NMPlatformIP4Route *route4;
const NMPlatformIP6Route *route6;
const NMIP4Config *ip4;
const NMIP6Config *ip6;
int addr_family = AF_UNSPEC;
char addr_family_char = '?';
const char *path;
gconstpointer ptr;
guint i;
if (self) {
addr_family = nm_ip_config_get_addr_family (self);
addr_family_char = nm_utils_addr_family_to_char (addr_family);
}
nm_log (level, domain, NULL, NULL,
"---- NMIP%cConfig %p (%s)",
addr_family_char,
self,
detail);
if (!self)
return;
path = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
if (path)
nm_log (level, domain, NULL, NULL, " path : %s", path);
if (addr_family == AF_INET) {
ip4 = NM_IP4_CONFIG (self);
nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &addr4) {
nm_log (level, domain, NULL, NULL, " address : %s",
nm_platform_ip4_address_to_string (addr4, NULL, 0));
}
nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route4) {
nm_log (level, domain, NULL, NULL, " route : %s",
nm_platform_ip4_route_to_string (route4, NULL, 0));
}
} else {
ip6 = NM_IP6_CONFIG (self);
nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &addr6) {
nm_log (level, domain, NULL, NULL, " address : %s",
nm_platform_ip6_address_to_string (addr6, NULL, 0));
}
nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route6) {
nm_log (level, domain, NULL, NULL, " route : %s",
nm_platform_ip6_route_to_string (route6, NULL, 0));
}
}
for (i = 0; i < nm_ip_config_get_num_nameservers (self); i++) {
char buf[NM_UTILS_INET_ADDRSTRLEN];
ptr = nm_ip_config_get_nameserver (self, i);
nm_log (level, domain, NULL, NULL, " dns : %s",
nm_utils_inet_ntop (addr_family, ptr, buf));
}
for (i = 0; i < nm_ip_config_get_num_domains (self); i++)
nm_log (level, domain, NULL, NULL, " domain : %s", nm_ip_config_get_domain (self, i));
for (i = 0; i < nm_ip_config_get_num_searches (self); i++)
nm_log (level, domain, NULL, NULL, " search : %s", nm_ip_config_get_search (self, i));
for (i = 0; i < nm_ip_config_get_num_dns_options (self); i++)
nm_log (level, domain, NULL, NULL, "dns-option: %s", nm_ip_config_get_dns_option (self, i));
nm_log (level, domain, NULL, NULL, " dns-prio : %d", nm_ip_config_get_dns_priority (self));
if (addr_family == AF_INET) {
ip4 = NM_IP4_CONFIG (self);
nm_log (level, domain, NULL, NULL,
" mtu : %"G_GUINT32_FORMAT" (source: %d)",
nm_ip4_config_get_mtu (ip4),
(int) nm_ip4_config_get_mtu_source (ip4));
nm_log (level, domain, NULL, NULL, " metered : %d",
(int) nm_ip4_config_get_metered (ip4));
}
}
/*****************************************************************************/
void
nm_ip4_config_reset_addresses (NMIP4Config *self)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
if (nm_dedup_multi_index_remove_idx (priv->multi_idx,
&priv->idx_ip4_addresses) > 0)
_notify_addresses (self);
}
static void
_add_address (NMIP4Config *self, const NMPObject *obj_new, const NMPlatformIP4Address *new)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
if (_nm_ip_config_add_obj (priv->multi_idx,
&priv->idx_ip4_addresses_,
priv->ifindex,
obj_new,
(const NMPlatformObject *) new,
TRUE,
FALSE,
NULL,
NULL))
_notify_addresses (self);
}
/**
* nm_ip4_config_add_address:
* @self: the #NMIP4Config
* @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_ip4_config_add_address (NMIP4Config *self, const NMPlatformIP4Address *new)
{
g_return_if_fail (self);
g_return_if_fail (new);
g_return_if_fail (new->plen <= 32);
g_return_if_fail (NM_IP4_CONFIG_GET_PRIVATE (self)->ifindex > 0);
_add_address (self, NULL, new);
}
void
_nmtst_ip4_config_del_address (NMIP4Config *self, guint i)
{
const NMPlatformIP4Address *a;
a = _nmtst_ip4_config_get_address (self, i);
if (!nm_ip4_config_nmpobj_remove (self,
NMP_OBJECT_UP_CAST (a)))
g_assert_not_reached ();
}
guint
nm_ip4_config_get_num_addresses (const NMIP4Config *self)
{
const NMDedupMultiHeadEntry *head_entry;
head_entry = nm_ip4_config_lookup_addresses (self);
return head_entry ? head_entry->len : 0;
}
const NMPlatformIP4Address *
nm_ip4_config_get_first_address (const NMIP4Config *self)
{
NMDedupMultiIter iter;
const NMPlatformIP4Address *a = NULL;
nm_ip_config_iter_ip4_address_for_each (&iter, self, &a)
return a;
return NULL;
}
const NMPlatformIP4Address *
_nmtst_ip4_config_get_address (const NMIP4Config *self, guint i)
{
NMDedupMultiIter iter = { };
const NMPlatformIP4Address *a = NULL;
guint j;
j = 0;
nm_ip_config_iter_ip4_address_for_each (&iter, self, &a) {
if (i == j)
return a;
j++;
}
g_return_val_if_reached (NULL);
}
gboolean
nm_ip4_config_address_exists (const NMIP4Config *self,
const NMPlatformIP4Address *needle)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
NMPObject obj_stack;
nmp_object_stackinit_id_ip4_address (&obj_stack,
priv->ifindex,
needle->address,
needle->plen,
needle->peer_address);
return !!nm_dedup_multi_index_lookup_obj (priv->multi_idx,
&priv->idx_ip4_addresses,
&obj_stack);
}
/*****************************************************************************/
static const NMDedupMultiEntry *
_lookup_route (const NMIP4Config *self,
const NMPObject *needle,
NMPlatformIPRouteCmpType cmp_type)
{
const NMIP4ConfigPrivate *priv;
nm_assert (NM_IS_IP4_CONFIG (self));
nm_assert (NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP4_ROUTE);
priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return _nm_ip_config_lookup_ip_route (priv->multi_idx,
&priv->idx_ip4_routes_,
needle,
cmp_type);
}
void
nm_ip4_config_reset_routes (NMIP4Config *self)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
if (nm_dedup_multi_index_remove_idx (priv->multi_idx,
&priv->idx_ip4_routes) > 0) {
if (nm_clear_nmp_object (&priv->best_default_route))
_notify (self, PROP_GATEWAY);
_notify_routes (self);
}
}
static void
_add_route (NMIP4Config *self,
const NMPObject *obj_new,
const NMPlatformIP4Route *new,
const NMPObject **out_obj_new)
{
NMIP4ConfigPrivate *priv = NM_IP4_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_IP4_ROUTE (obj_new)));
if (_nm_ip_config_add_obj (priv->multi_idx,
&priv->idx_ip4_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_ip4_config_add_route:
* @self: the #NMIP4Config
* @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_ip4_config_add_route (NMIP4Config *self,
const NMPlatformIP4Route *new,
const NMPObject **out_obj_new)
{
g_return_if_fail (self);
g_return_if_fail (new);
g_return_if_fail (new->plen <= 32);
g_return_if_fail (NM_IP4_CONFIG_GET_PRIVATE (self)->ifindex > 0);
_add_route (self, NULL, new, out_obj_new);
}
void
_nmtst_ip4_config_del_route (NMIP4Config *self, guint i)
{
const NMPlatformIP4Route *r;
r = _nmtst_ip4_config_get_route (self, i);
if (!nm_ip4_config_nmpobj_remove (self,
NMP_OBJECT_UP_CAST (r)))
g_assert_not_reached ();
}
guint
nm_ip4_config_get_num_routes (const NMIP4Config *self)
{
const NMDedupMultiHeadEntry *head_entry;
head_entry = nm_ip4_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 NMPlatformIP4Route *
_nmtst_ip4_config_get_route (const NMIP4Config *self, guint i)
{
NMDedupMultiIter iter;
const NMPlatformIP4Route *r = NULL;
guint j;
j = 0;
nm_ip_config_iter_ip4_route_for_each (&iter, self, &r) {
if (i == j)
return r;
j++;
}
g_return_val_if_reached (NULL);
}
const NMPlatformIP4Route *
nm_ip4_config_get_direct_route_for_host (const NMIP4Config *self,
in_addr_t host,
guint32 route_table)
{
const NMPlatformIP4Route *best_route = NULL;
const NMPlatformIP4Route *item;
NMDedupMultiIter ipconf_iter;
g_return_val_if_fail (host, NULL);
nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &item) {
if (item->gateway != 0)
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_ip4_address_clear_host_address (host, item->plen) != nm_utils_ip4_address_clear_host_address (item->network, item->plen))
continue;
if (best_route && best_route->metric <= item->metric)
continue;
best_route = item;
}
return best_route;
}
/*****************************************************************************/
void
nm_ip4_config_reset_nameservers (NMIP4Config *self)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
if (priv->nameservers->len != 0) {
g_array_set_size (priv->nameservers, 0);
nm_gobject_notify_together (self, PROP_NAMESERVER_DATA,
PROP_NAMESERVERS);
}
}
void
nm_ip4_config_add_nameserver (NMIP4Config *self, guint32 new)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
int i;
g_return_if_fail (new != 0);
for (i = 0; i < priv->nameservers->len; i++)
if (new == g_array_index (priv->nameservers, guint32, i))
return;
g_array_append_val (priv->nameservers, new);
nm_gobject_notify_together (self, PROP_NAMESERVER_DATA,
PROP_NAMESERVERS);
}
void
nm_ip4_config_del_nameserver (NMIP4Config *self, guint i)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
g_return_if_fail (i < priv->nameservers->len);
g_array_remove_index (priv->nameservers, i);
nm_gobject_notify_together (self, PROP_NAMESERVER_DATA,
PROP_NAMESERVERS);
}
guint
nm_ip4_config_get_num_nameservers (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return priv->nameservers->len;
}
guint32
nm_ip4_config_get_nameserver (const NMIP4Config *self, guint i)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return g_array_index (priv->nameservers, guint32, i);
}
const in_addr_t *
_nm_ip4_config_get_nameserver (const NMIP4Config *self, guint i)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return &g_array_index (priv->nameservers, guint32, i);
}
/*****************************************************************************/
gboolean
_nm_ip_config_check_and_add_domain (GPtrArray *array, const char *domain)
{
char *copy = NULL;
size_t len;
g_return_val_if_fail (domain, FALSE);
g_return_val_if_fail (domain[0] != '\0', FALSE);
if (domain[0] == '.' || strstr (domain, ".."))
return FALSE;
len = strlen (domain);
if (domain[len - 1] == '.')
domain = copy = g_strndup (domain, len - 1);
if (nm_utils_strv_find_first ((char **) array->pdata, array->len, domain) >= 0) {
g_free (copy);
return FALSE;
}
g_ptr_array_add (array, copy ?: g_strdup (domain));
return TRUE;
}
void
nm_ip4_config_reset_domains (NMIP4Config *self)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
if (priv->domains->len != 0) {
g_ptr_array_set_size (priv->domains, 0);
_notify (self, PROP_DOMAINS);
}
}
void
nm_ip4_config_add_domain (NMIP4Config *self, const char *domain)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
if (_nm_ip_config_check_and_add_domain (priv->domains, domain))
_notify (self, PROP_DOMAINS);
}
void
nm_ip4_config_del_domain (NMIP4Config *self, guint i)
{
NMIP4ConfigPrivate *priv = NM_IP4_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_ip4_config_get_num_domains (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return priv->domains->len;
}
const char *
nm_ip4_config_get_domain (const NMIP4Config *self, guint i)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return g_ptr_array_index (priv->domains, i);
}
/*****************************************************************************/
void
nm_ip4_config_reset_searches (NMIP4Config *self)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
if (priv->searches->len != 0) {
g_ptr_array_set_size (priv->searches, 0);
_notify (self, PROP_SEARCHES);
}
}
void
nm_ip4_config_add_search (NMIP4Config *self, const char *search)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
if (_nm_ip_config_check_and_add_domain (priv->searches, search))
_notify (self, PROP_SEARCHES);
}
void
nm_ip4_config_del_search (NMIP4Config *self, guint i)
{
NMIP4ConfigPrivate *priv = NM_IP4_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_ip4_config_get_num_searches (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return priv->searches->len;
}
const char *
nm_ip4_config_get_search (const NMIP4Config *self, guint i)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return g_ptr_array_index (priv->searches, i);
}
/*****************************************************************************/
void
nm_ip4_config_reset_dns_options (NMIP4Config *self)
{
NMIP4ConfigPrivate *priv = NM_IP4_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_ip4_config_add_dns_option (NMIP4Config *self, const char *new)
{
NMIP4ConfigPrivate *priv = NM_IP4_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_ip4_config_del_dns_option(NMIP4Config *self, guint i)
{
NMIP4ConfigPrivate *priv = NM_IP4_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_ip4_config_get_num_dns_options (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return priv->dns_options->len;
}
const char *
nm_ip4_config_get_dns_option (const NMIP4Config *self, guint i)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return g_ptr_array_index (priv->dns_options, i);
}
/*****************************************************************************/
NMSettingConnectionMdns
nm_ip4_config_mdns_get (const NMIP4Config *self)
{
return NM_IP4_CONFIG_GET_PRIVATE (self)->mdns;
}
void
nm_ip4_config_mdns_set (NMIP4Config *self,
NMSettingConnectionMdns mdns)
{
NM_IP4_CONFIG_GET_PRIVATE (self)->mdns = mdns;
}
NMSettingConnectionLlmnr
nm_ip4_config_llmnr_get (const NMIP4Config *self)
{
return NM_IP4_CONFIG_GET_PRIVATE (self)->llmnr;
}
void
nm_ip4_config_llmnr_set (NMIP4Config *self,
NMSettingConnectionLlmnr llmnr)
{
NM_IP4_CONFIG_GET_PRIVATE (self)->llmnr = llmnr;
}
/*****************************************************************************/
NMIPConfigFlags
nm_ip4_config_get_config_flags (const NMIP4Config *self)
{
return NM_IP4_CONFIG_GET_PRIVATE (self)->config_flags;
}
void
nm_ip4_config_set_config_flags (NMIP4Config *self, NMIPConfigFlags flags, NMIPConfigFlags mask)
{
NMIP4ConfigPrivate *priv = NM_IP4_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_ip4_config_set_dns_priority (NMIP4Config *self, int priority)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
if (priority != priv->dns_priority) {
priv->dns_priority = priority;
_notify (self, PROP_DNS_PRIORITY);
}
}
int
nm_ip4_config_get_dns_priority (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return priv->dns_priority;
}
/*****************************************************************************/
void
nm_ip4_config_reset_nis_servers (NMIP4Config *self)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
g_array_set_size (priv->nis, 0);
}
void
nm_ip4_config_add_nis_server (NMIP4Config *self, guint32 nis)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
int i;
for (i = 0; i < priv->nis->len; i++)
if (nis == g_array_index (priv->nis, guint32, i))
return;
g_array_append_val (priv->nis, nis);
}
void
nm_ip4_config_del_nis_server (NMIP4Config *self, guint i)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
g_return_if_fail (i < priv->nis->len);
g_array_remove_index (priv->nis, i);
}
guint
nm_ip4_config_get_num_nis_servers (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return priv->nis->len;
}
guint32
nm_ip4_config_get_nis_server (const NMIP4Config *self, guint i)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return g_array_index (priv->nis, guint32, i);
}
void
nm_ip4_config_set_nis_domain (NMIP4Config *self, const char *domain)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
g_free (priv->nis_domain);
priv->nis_domain = g_strdup (domain);
}
const char *
nm_ip4_config_get_nis_domain (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return priv->nis_domain;
}
/*****************************************************************************/
void
nm_ip4_config_reset_wins (NMIP4Config *self)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
if (priv->wins->len != 0) {
g_array_set_size (priv->wins, 0);
nm_gobject_notify_together (self, PROP_WINS_SERVER_DATA,
PROP_WINS_SERVERS);
}
}
void
nm_ip4_config_add_wins (NMIP4Config *self, guint32 wins)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
int i;
g_return_if_fail (wins != 0);
for (i = 0; i < priv->wins->len; i++)
if (wins == g_array_index (priv->wins, guint32, i))
return;
g_array_append_val (priv->wins, wins);
nm_gobject_notify_together (self, PROP_WINS_SERVER_DATA,
PROP_WINS_SERVERS);
}
void
nm_ip4_config_del_wins (NMIP4Config *self, guint i)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
g_return_if_fail (i < priv->wins->len);
g_array_remove_index (priv->wins, i);
nm_gobject_notify_together (self, PROP_WINS_SERVER_DATA,
PROP_WINS_SERVERS);
}
guint
nm_ip4_config_get_num_wins (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return priv->wins->len;
}
guint32
nm_ip4_config_get_wins (const NMIP4Config *self, guint i)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return g_array_index (priv->wins, guint32, i);
}
/*****************************************************************************/
void
nm_ip4_config_set_mtu (NMIP4Config *self, guint32 mtu, NMIPConfigSource source)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
if (!mtu)
source = NM_IP_CONFIG_SOURCE_UNKNOWN;
priv->mtu = mtu;
priv->mtu_source = source;
}
guint32
nm_ip4_config_get_mtu (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return priv->mtu;
}
NMIPConfigSource
nm_ip4_config_get_mtu_source (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return priv->mtu_source;
}
/*****************************************************************************/
void
nm_ip4_config_set_metered (NMIP4Config *self, gboolean metered)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
priv->metered = metered;
}
gboolean
nm_ip4_config_get_metered (const NMIP4Config *self)
{
const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
return priv->metered;
}
/*****************************************************************************/
const NMPObject *
nm_ip4_config_nmpobj_lookup (const NMIP4Config *self, const NMPObject *needle)
{
const NMIP4ConfigPrivate *priv;
const NMDedupMultiIdxType *idx_type;
g_return_val_if_fail (NM_IS_IP4_CONFIG (self), NULL);
priv = NM_IP4_CONFIG_GET_PRIVATE (self);
switch (NMP_OBJECT_GET_TYPE (needle)) {
case NMP_OBJECT_TYPE_IP4_ADDRESS:
idx_type = &priv->idx_ip4_addresses;
break;
case NMP_OBJECT_TYPE_IP4_ROUTE:
idx_type = &priv->idx_ip4_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_ip4_config_nmpobj_remove (NMIP4Config *self,
const NMPObject *needle)
{
NMIP4ConfigPrivate *priv;
NMDedupMultiIdxType *idx_type;
nm_auto_nmpobj const NMPObject *obj_old = NULL;
guint n;
g_return_val_if_fail (NM_IS_IP4_CONFIG (self), FALSE);
priv = NM_IP4_CONFIG_GET_PRIVATE (self);
switch (NMP_OBJECT_GET_TYPE (needle)) {
case NMP_OBJECT_TYPE_IP4_ADDRESS:
idx_type = &priv->idx_ip4_addresses;
break;
case NMP_OBJECT_TYPE_IP4_ROUTE:
idx_type = &priv->idx_ip4_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_IP4_ADDRESS:
_notify_addresses (self);
break;
case NMP_OBJECT_TYPE_IP4_ROUTE:
if (priv->best_default_route == obj_old) {
if (_nm_ip_config_best_default_route_set (&priv->best_default_route,
_nm_ip4_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));
}
void
nm_ip4_config_hash (const NMIP4Config *self, GChecksum *sum, gboolean dns_only)
{
guint i;
const char *s;
NMDedupMultiIter ipconf_iter;
const NMPlatformIP4Address *address;
const NMPlatformIP4Route *route;
int val;
g_return_if_fail (self);
g_return_if_fail (sum);
if (!dns_only) {
nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, self, &address) {
hash_u32 (sum, address->address);
hash_u32 (sum, address->plen);
hash_u32 (sum, address->peer_address & _nm_utils_ip4_prefix_to_netmask (address->plen));
}
nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route) {
hash_u32 (sum, route->network);
hash_u32 (sum, route->plen);
hash_u32 (sum, route->gateway);
hash_u32 (sum, route->metric);
}
for (i = 0; i < nm_ip4_config_get_num_nis_servers (self); i++)
hash_u32 (sum, nm_ip4_config_get_nis_server (self, i));
s = nm_ip4_config_get_nis_domain (self);
if (s)
g_checksum_update (sum, (const guint8 *) s, strlen (s));
}
for (i = 0; i < nm_ip4_config_get_num_nameservers (self); i++)
hash_u32 (sum, nm_ip4_config_get_nameserver (self, i));
for (i = 0; i < nm_ip4_config_get_num_wins (self); i++)
hash_u32 (sum, nm_ip4_config_get_wins (self, i));
for (i = 0; i < nm_ip4_config_get_num_domains (self); i++) {
s = nm_ip4_config_get_domain (self, i);
g_checksum_update (sum, (const guint8 *) s, strlen (s));
}
for (i = 0; i < nm_ip4_config_get_num_searches (self); i++) {
s = nm_ip4_config_get_search (self, i);
g_checksum_update (sum, (const guint8 *) s, strlen (s));
}
for (i = 0; i < nm_ip4_config_get_num_dns_options (self); i++) {
s = nm_ip4_config_get_dns_option (self, i);
g_checksum_update (sum, (const guint8 *) s, strlen (s));
}
val = nm_ip4_config_mdns_get (self);
if (val != NM_SETTING_CONNECTION_MDNS_DEFAULT)
g_checksum_update (sum, (const guint8 *) &val, sizeof (val));
val = nm_ip4_config_llmnr_get (self);
if (val != NM_SETTING_CONNECTION_LLMNR_DEFAULT)
g_checksum_update (sum, (const guint8 *) &val, sizeof (val));
/* FIXME(ip-config-checksum): the DNS priority should be considered relevant
* and added into the checksum as well, but this can't be done right now
* because in the DNS manager we rely on the fact that an empty
* configuration (i.e. just created) has a zero checksum. This is needed to
* avoid rewriting resolv.conf when there is no change.
*
* The DNS priority initial value depends on the connection type (VPN or
* not), so it's a bit difficult to add it to checksum maintaining the
* assumption of checksum(empty)=0
*/
}
/**
* nm_ip4_config_equal:
* @a: first config to compare
* @b: second config to compare
*
* Compares two #NMIP4Configs 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_ip4_config_equal (const NMIP4Config *a, const NMIP4Config *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_ip4_config_hash (a, a_checksum, FALSE);
if (b)
nm_ip4_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
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
NMIP4Config *self = NM_IP4_CONFIG (object);
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
const NMDedupMultiHeadEntry *head_entry;
NMDedupMultiIter ipconf_iter;
const NMPlatformIP4Route *route;
GVariantBuilder builder_data, builder_legacy;
guint i;
char addr_str[NM_UTILS_INET_ADDRSTRLEN];
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)
goto out_addresses_cached;
g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("aa{sv}"));
g_variant_builder_init (&builder_legacy, G_VARIANT_TYPE ("aau"));
head_entry = nm_ip4_config_lookup_addresses (self);
if (head_entry) {
gs_free const NMPObject **addresses = NULL;
guint naddr;
addresses = (const NMPObject **) nm_dedup_multi_objs_to_array_head (head_entry, NULL, NULL, &naddr);
nm_assert (addresses && naddr);
g_qsort_with_data (addresses,
naddr,
sizeof (addresses[0]),
_addresses_sort_cmp,
NULL);
/* Build address data variant */
for (i = 0; i < naddr; i++) {
GVariantBuilder addr_builder;
const NMPlatformIP4Address *address = NMP_OBJECT_CAST_IP4_ADDRESS (addresses[i]);
g_variant_builder_init (&addr_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&addr_builder, "{sv}",
"address",
g_variant_new_string (_nm_utils_inet4_ntop (address->address, addr_str)));
g_variant_builder_add (&addr_builder, "{sv}",
"prefix",
g_variant_new_uint32 (address->plen));
if (address->peer_address != address->address) {
g_variant_builder_add (&addr_builder, "{sv}",
"peer",
g_variant_new_string (_nm_utils_inet4_ntop (address->peer_address, addr_str)));
}
if (*address->label) {
g_variant_builder_add (&addr_builder, "{sv}",
NM_IP_ADDRESS_ATTRIBUTE_LABEL,
g_variant_new_string (address->label));
}
g_variant_builder_add (&builder_data, "a{sv}", &addr_builder);
{
const guint32 dbus_addr[3] = {
address->address,
address->plen,
( i == 0
&& priv->best_default_route)
? NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway
: (guint32) 0,
};
g_variant_builder_add (&builder_legacy, "@au",
g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
dbus_addr, 3, sizeof (guint32)));
}
}
}
priv->address_data_variant = g_variant_ref_sink (g_variant_builder_end (&builder_data));
priv->addresses_variant = g_variant_ref_sink (g_variant_builder_end (&builder_legacy));
out_addresses_cached:
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)
goto out_routes_cached;
g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("aa{sv}"));
g_variant_builder_init (&builder_legacy, G_VARIANT_TYPE ("aau"));
nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route) {
GVariantBuilder route_builder;
nm_assert (_route_valid (route));
g_variant_builder_init (&route_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&route_builder, "{sv}",
"dest",
g_variant_new_string (_nm_utils_inet4_ntop (route->network, addr_str)));
g_variant_builder_add (&route_builder, "{sv}",
"prefix",
g_variant_new_uint32 (route->plen));
if (route->gateway) {
g_variant_builder_add (&route_builder, "{sv}",
"next-hop",
g_variant_new_string (_nm_utils_inet4_ntop (route->gateway, addr_str)));
}
g_variant_builder_add (&route_builder, "{sv}",
"metric",
g_variant_new_uint32 (route->metric));
if (!nm_platform_route_table_is_main (route->table_coerced)) {
g_variant_builder_add (&route_builder, "{sv}",
"table",
g_variant_new_uint32 (nm_platform_route_table_uncoerce (route->table_coerced, TRUE)));
}
g_variant_builder_add (&builder_data, "a{sv}", &route_builder);
/* legacy versions of nm_ip4_route_set_prefix() in libnm-util assert that the
* plen is positive. Skip the default routes not to break older clients. */
if ( nm_platform_route_table_is_main (route->table_coerced)
&& !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
const guint32 dbus_route[4] = {
route->network,
route->plen,
route->gateway,
route->metric,
};
g_variant_builder_add (&builder_legacy, "@au",
g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
dbus_route, 4, sizeof (guint32)));
}
}
priv->route_data_variant = g_variant_ref_sink (g_variant_builder_end (&builder_data));
priv->routes_variant = g_variant_ref_sink (g_variant_builder_end (&builder_legacy));
out_routes_cached:
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_inet4_ntop_dup (NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway));
} else
g_value_set_string (value, NULL);
break;
case PROP_NAMESERVER_DATA:
g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("aa{sv}"));
for (i = 0; i < priv->nameservers->len; i++) {
GVariantBuilder nested_builder;
_nm_utils_inet4_ntop (g_array_index (priv->nameservers, in_addr_t, i),
addr_str);
g_variant_builder_init (&nested_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&nested_builder, "{sv}",
"address",
g_variant_new_string (addr_str));
g_variant_builder_add (&builder_data, "a{sv}", &nested_builder);
}
g_value_take_variant (value,
g_variant_builder_end (&builder_data));
break;
case PROP_NAMESERVERS:
g_value_take_variant (value,
g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
priv->nameservers->data,
priv->nameservers->len,
sizeof (guint32)));
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;
case PROP_WINS_SERVER_DATA:
g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("as"));
for (i = 0; i < priv->wins->len; i++) {
g_variant_builder_add (&builder_data,
"s",
_nm_utils_inet4_ntop (g_array_index (priv->wins, in_addr_t, i),
addr_str));
}
g_value_take_variant (value,
g_variant_builder_end (&builder_data));
break;
case PROP_WINS_SERVERS:
g_value_take_variant (value,
g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
priv->wins->data,
priv->wins->len,
sizeof (guint32)));
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)
{
NMIP4Config *self = NM_IP4_CONFIG (object);
NMIP4ConfigPrivate *priv = NM_IP4_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_ip4_config_init (NMIP4Config *self)
{
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
nm_ip_config_dedup_multi_idx_type_init ((NMIPConfigDedupMultiIdxType *) &priv->idx_ip4_addresses,
NMP_OBJECT_TYPE_IP4_ADDRESS);
nm_ip_config_dedup_multi_idx_type_init ((NMIPConfigDedupMultiIdxType *) &priv->idx_ip4_routes,
NMP_OBJECT_TYPE_IP4_ROUTE);
priv->mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
priv->llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
priv->nameservers = g_array_new (FALSE, FALSE, sizeof (guint32));
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);
priv->nis = g_array_new (FALSE, TRUE, sizeof (guint32));
priv->wins = g_array_new (FALSE, TRUE, sizeof (guint32));
}
NMIP4Config *
nm_ip4_config_new (NMDedupMultiIndex *multi_idx, int ifindex)
{
g_return_val_if_fail (ifindex >= -1, NULL);
return (NMIP4Config *) g_object_new (NM_TYPE_IP4_CONFIG,
NM_IP4_CONFIG_MULTI_IDX, multi_idx,
NM_IP4_CONFIG_IFINDEX, ifindex,
NULL);
}
static void
finalize (GObject *object)
{
NMIP4Config *self = NM_IP4_CONFIG (object);
NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
nm_clear_nmp_object (&priv->best_default_route);
nm_dedup_multi_index_remove_idx (priv->multi_idx, &priv->idx_ip4_addresses);
nm_dedup_multi_index_remove_idx (priv->multi_idx, &priv->idx_ip4_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_array_unref (priv->nis);
g_free (priv->nis_domain);
g_array_unref (priv->wins);
G_OBJECT_CLASS (nm_ip4_config_parent_class)->finalize (object);
nm_dedup_multi_index_unref (priv->multi_idx);
}
static const NMDBusInterfaceInfoExtended interface_info_ip4_config = {
.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
NM_DBUS_INTERFACE_IP4_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", "aau", NM_IP4_CONFIG_ADDRESSES),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("AddressData", "aa{sv}", NM_IP4_CONFIG_ADDRESS_DATA),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Gateway", "s", NM_IP4_CONFIG_GATEWAY),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Routes", "aau", NM_IP4_CONFIG_ROUTES),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("RouteData", "aa{sv}", NM_IP4_CONFIG_ROUTE_DATA),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("NameserverData", "aa{sv}", NM_IP4_CONFIG_NAMESERVER_DATA),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Nameservers", "au", NM_IP4_CONFIG_NAMESERVERS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Domains", "as", NM_IP4_CONFIG_DOMAINS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Searches", "as", NM_IP4_CONFIG_SEARCHES),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("DnsOptions", "as", NM_IP4_CONFIG_DNS_OPTIONS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("DnsPriority", "i", NM_IP4_CONFIG_DNS_PRIORITY),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("WinsServerData", "as", NM_IP4_CONFIG_WINS_SERVER_DATA),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WinsServers", "au", NM_IP4_CONFIG_WINS_SERVERS),
),
),
.legacy_property_changed = TRUE,
};
static void
nm_ip4_config_class_init (NMIP4ConfigClass *config_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (config_class);
NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (config_class);
dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/IP4Config");
dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_ip4_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_IP4_CONFIG_MULTI_IDX, "", "",
G_PARAM_WRITABLE
| G_PARAM_CONSTRUCT_ONLY
| G_PARAM_STATIC_STRINGS);
obj_properties[PROP_IFINDEX] =
g_param_spec_int (NM_IP4_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_IP4_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_IP4_CONFIG_ADDRESSES, "", "",
G_VARIANT_TYPE ("aau"),
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_ROUTE_DATA] =
g_param_spec_variant (NM_IP4_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_IP4_CONFIG_ROUTES, "", "",
G_VARIANT_TYPE ("aau"),
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_GATEWAY] =
g_param_spec_string (NM_IP4_CONFIG_GATEWAY, "", "",
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_NAMESERVER_DATA] =
g_param_spec_variant (NM_IP4_CONFIG_NAMESERVER_DATA, "", "",
G_VARIANT_TYPE ("aa{sv}"),
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_NAMESERVERS] =
g_param_spec_variant (NM_IP4_CONFIG_NAMESERVERS, "", "",
G_VARIANT_TYPE ("au"),
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_DOMAINS] =
g_param_spec_boxed (NM_IP4_CONFIG_DOMAINS, "", "",
G_TYPE_STRV,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_SEARCHES] =
g_param_spec_boxed (NM_IP4_CONFIG_SEARCHES, "", "",
G_TYPE_STRV,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_DNS_OPTIONS] =
g_param_spec_boxed (NM_IP4_CONFIG_DNS_OPTIONS, "", "",
G_TYPE_STRV,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_DNS_PRIORITY] =
g_param_spec_int (NM_IP4_CONFIG_DNS_PRIORITY, "", "",
G_MININT32, G_MAXINT32, 0,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_WINS_SERVER_DATA] =
g_param_spec_variant (NM_IP4_CONFIG_WINS_SERVER_DATA, "", "",
G_VARIANT_TYPE ("as"),
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_WINS_SERVERS] =
g_param_spec_variant (NM_IP4_CONFIG_WINS_SERVERS, "", "",
G_VARIANT_TYPE ("au"),
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
}