l3cfg: track externally removed addresses/routes

We want to allow the user to externally remove IP addresses
and routes, and NetworkManager not re-adding them until a full reapply
happens. For that, we need to keep track of IP addresses that were
present, but no longer are.
This commit is contained in:
Thomas Haller 2020-07-29 08:39:12 +02:00
parent 3ac668bc1a
commit e50597559b
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
5 changed files with 389 additions and 31 deletions

View file

@ -528,36 +528,60 @@ nm_l3_config_data_lookup_route (const NML3ConfigData *self,
nmp_object_stackinit (&obj_stack, NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4), needle));
}
const NMDedupMultiHeadEntry *
nm_l3_config_data_lookup_objs (const NML3ConfigData *self, NMPObjectType obj_type)
const NMDedupMultiIdxType *
nm_l3_config_data_lookup_index (const NML3ConfigData *self, NMPObjectType obj_type)
{
const DedupMultiIdxType *idx;
nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE));
switch (obj_type) {
case NMP_OBJECT_TYPE_IP4_ADDRESS:
idx = &self->idx_addresses_4;
break;
return &self->idx_addresses_4.parent;
case NMP_OBJECT_TYPE_IP6_ADDRESS:
idx = &self->idx_addresses_6;
break;
return &self->idx_addresses_6.parent;
case NMP_OBJECT_TYPE_IP4_ROUTE:
idx = &self->idx_routes_4;
break;
return &self->idx_routes_4.parent;
case NMP_OBJECT_TYPE_IP6_ROUTE:
idx = &self->idx_routes_6;
break;
return &self->idx_routes_6.parent;
default:
nm_assert_not_reached ();
return NULL;
}
}
return nm_dedup_multi_index_lookup_head (self->multi_idx, &idx->parent, NULL);
const NMDedupMultiHeadEntry *
nm_l3_config_data_lookup_objs (const NML3ConfigData *self, NMPObjectType obj_type)
{
return nm_dedup_multi_index_lookup_head (self->multi_idx,
nm_l3_config_data_lookup_index (self, obj_type),
NULL);
}
const NMDedupMultiEntry *
nm_l3_config_data_lookup_obj (const NML3ConfigData *self,
const NMPObject *obj)
{
const NMDedupMultiIdxType *idx;
nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE));
idx = nm_l3_config_data_lookup_index (self,
NMP_OBJECT_GET_TYPE (obj));
return nm_dedup_multi_index_lookup_obj (self->multi_idx,
idx,
obj);
}
/*****************************************************************************/
NMDedupMultiIndex *
nm_l3_config_data_get_multi_idx (const NML3ConfigData *self)
{
nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE));
return self->multi_idx;
}
int
nm_l3_config_data_get_ifindex (const NML3ConfigData *self)
{

View file

@ -122,6 +122,8 @@ void nm_l3_config_data_add_dependent_routes (NML3ConfigData *self,
int nm_l3_config_data_get_ifindex (const NML3ConfigData *self);
NMDedupMultiIndex *nm_l3_config_data_get_multi_idx (const NML3ConfigData *self);
static inline gboolean
NM_IS_L3_CONFIG_DATA (const NML3ConfigData *self)
{
@ -147,6 +149,12 @@ nm_l3_config_data_equal (const NML3ConfigData *a, const NML3ConfigData *b)
/*****************************************************************************/
const NMDedupMultiIdxType *nm_l3_config_data_lookup_index (const NML3ConfigData *self,
NMPObjectType obj_type);
const NMDedupMultiEntry *nm_l3_config_data_lookup_obj (const NML3ConfigData *self,
const NMPObject *obj);
const NMDedupMultiEntry *nm_l3_config_data_lookup_route_obj (const NML3ConfigData *self,
const NMPObject *needle);

View file

@ -41,8 +41,26 @@ typedef struct _NML3CfgPrivate {
GHashTable *routes_temporary_not_available_hash;
GHashTable *externally_removed_objs_hash;
guint64 pseudo_timestamp_counter;
union {
struct {
guint externally_removed_objs_cnt_addresses_6;
guint externally_removed_objs_cnt_addresses_4;
};
guint externally_removed_objs_cnt_addresses_x[2];
};
union {
struct {
guint externally_removed_objs_cnt_routes_6;
guint externally_removed_objs_cnt_routes_4;
};
guint externally_removed_objs_cnt_routes_x[2];
};
guint routes_temporary_not_available_id;
} NML3CfgPrivate;
@ -71,6 +89,15 @@ static void _property_emit_notify (NML3Cfg *self, NML3CfgPropertyEmitType emit_t
/*****************************************************************************/
static
NM_UTILS_ENUM2STR_DEFINE (_l3_cfg_commit_type_to_string, NML3CfgCommitType,
NM_UTILS_ENUM2STR (NM_L3_CFG_COMMIT_TYPE_ASSUME, "assume"),
NM_UTILS_ENUM2STR (NM_L3_CFG_COMMIT_TYPE_UPDATE, "update"),
NM_UTILS_ENUM2STR (NM_L3_CFG_COMMIT_TYPE_REAPPLY, "reapply"),
);
/*****************************************************************************/
static void
_l3cfg_emit_signal_notify (NML3Cfg *self,
NML3ConfigNotifyType notify_type,
@ -88,6 +115,188 @@ _l3cfg_emit_signal_notify (NML3Cfg *self,
/*****************************************************************************/
static guint *
_l3cfg_externally_removed_objs_counter (NML3Cfg *self,
NMPObjectType obj_type)
{
switch (obj_type) {
case NMP_OBJECT_TYPE_IP4_ADDRESS:
return &self->priv.p->externally_removed_objs_cnt_addresses_4;
case NMP_OBJECT_TYPE_IP6_ADDRESS:
return &self->priv.p->externally_removed_objs_cnt_addresses_6;
case NMP_OBJECT_TYPE_IP4_ROUTE:
return &self->priv.p->externally_removed_objs_cnt_routes_4;
case NMP_OBJECT_TYPE_IP6_ROUTE:
return &self->priv.p->externally_removed_objs_cnt_routes_6;
default:
return nm_assert_unreachable_val (NULL);
}
}
static void
_l3cfg_externally_removed_objs_drop (NML3Cfg *self,
int addr_family)
{
const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family);
GHashTableIter iter;
const NMPObject *obj;
nm_assert (NM_IS_L3CFG (self));
nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6));
if (addr_family == AF_UNSPEC) {
self->priv.p->externally_removed_objs_cnt_addresses_4 = 0;
self->priv.p->externally_removed_objs_cnt_addresses_6 = 0;
self->priv.p->externally_removed_objs_cnt_routes_4 = 0;
self->priv.p->externally_removed_objs_cnt_routes_6 = 0;
if (g_hash_table_size (self->priv.p->externally_removed_objs_hash) > 0)
_LOGD ("externally-removed: untrack all");
nm_clear_pointer (&self->priv.p->externally_removed_objs_hash, g_hash_table_unref);
return;
}
if ( self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] == 0
&& self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] == 0)
return;
_LOGD ("externally-removed: untrack IPv%c",
nm_utils_addr_family_to_char (addr_family));
g_hash_table_iter_init (&iter, self->priv.p->externally_removed_objs_hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &obj, NULL)) {
nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ADDRESS,
NMP_OBJECT_TYPE_IP6_ADDRESS,
NMP_OBJECT_TYPE_IP4_ROUTE,
NMP_OBJECT_TYPE_IP6_ROUTE));
if (NMP_OBJECT_GET_ADDR_FAMILY (obj) != addr_family)
g_hash_table_iter_remove (&iter);
}
self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] = 0;
self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] = 0;
if ( self->priv.p->externally_removed_objs_cnt_addresses_x[!IS_IPv4] == 0
&& self->priv.p->externally_removed_objs_cnt_routes_x[!IS_IPv4] == 0)
nm_clear_pointer (&self->priv.p->externally_removed_objs_hash, g_hash_table_unref);
}
static void
_l3cfg_externally_removed_objs_drop_unused (NML3Cfg *self)
{
GHashTableIter h_iter;
const NMPObject *obj;
char sbuf[sizeof (_nm_utils_to_string_buffer)];
nm_assert (NM_IS_L3CFG (self));
if (!self->priv.p->externally_removed_objs_hash)
return;
if (!self->priv.p->combined_l3cfg) {
_l3cfg_externally_removed_objs_drop (self, AF_UNSPEC);
return;
}
g_hash_table_iter_init (&h_iter, self->priv.p->externally_removed_objs_hash);
while (g_hash_table_iter_next (&h_iter, (gpointer *) &obj, NULL)) {
if (!nm_l3_config_data_lookup_route_obj (self->priv.p->combined_l3cfg,
obj)) {
/* The object is no longer tracked in the configuration.
* The externally_removed_objs_hash is to prevent adding entires that were
* removed externally, so if we don't plan to add the entry, we no longer need to track
* it. */
(*(_l3cfg_externally_removed_objs_counter (self, NMP_OBJECT_GET_TYPE (obj))))--;
g_hash_table_iter_remove (&h_iter);
_LOGD ("externally-removed: untrack %s",
nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf)));
}
}
}
static void
_l3cfg_externally_removed_objs_track (NML3Cfg *self,
const NMPObject *obj,
gboolean is_removed)
{
char sbuf[1000];
nm_assert (NM_IS_L3CFG (self));
if (!self->priv.p->combined_l3cfg)
return;
if (!is_removed) {
/* the object is still (or again) present. It no longer gets hidden. */
if (self->priv.p->externally_removed_objs_hash) {
if (g_hash_table_remove (self->priv.p->externally_removed_objs_hash,
obj)) {
(*(_l3cfg_externally_removed_objs_counter (self,
NMP_OBJECT_GET_TYPE (obj))))--;
_LOGD ("externally-removed: untrack %s",
nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf)));
}
}
return;
}
if (!nm_l3_config_data_lookup_route_obj (self->priv.p->combined_l3cfg,
obj)) {
/* we don't care about this object, so there is nothing to hide hide */
return;
}
if (G_UNLIKELY (!self->priv.p->externally_removed_objs_hash)) {
self->priv.p->externally_removed_objs_hash = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
(GEqualFunc) nmp_object_id_equal,
(GDestroyNotify) nmp_object_unref,
NULL);
}
if (g_hash_table_add (self->priv.p->externally_removed_objs_hash,
(gpointer) nmp_object_ref (obj))) {
(*(_l3cfg_externally_removed_objs_counter (self,
NMP_OBJECT_GET_TYPE (obj))))++;
_LOGD ("externally-removed: track %s",
nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf)));
}
}
static void
_l3cfg_externally_removed_objs_pickup (NML3Cfg *self,
int addr_family)
{
const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family);
NMDedupMultiIter iter;
const NMPObject *obj;
if (!self->priv.p->combined_l3cfg)
return;
nm_l3_config_data_iter_obj_for_each (iter, self->priv.p->combined_l3cfg, obj, NMP_OBJECT_TYPE_IP_ADDRESS (IS_IPv4)) {
if (!nm_platform_lookup_entry (self->priv.platform,
NMP_CACHE_ID_TYPE_OBJECT_TYPE,
obj))
_l3cfg_externally_removed_objs_track (self, obj, TRUE);
}
nm_l3_config_data_iter_obj_for_each (iter, self->priv.p->combined_l3cfg, obj, NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4)) {
if (!nm_platform_lookup_entry (self->priv.platform,
NMP_CACHE_ID_TYPE_OBJECT_TYPE,
obj))
_l3cfg_externally_removed_objs_track (self, obj, TRUE);
}
}
static gboolean
_l3cfg_externally_removed_objs_filter (/* const NMDedupMultiObj * */ gconstpointer o,
gpointer user_data)
{
const NMPObject *obj = o;
GHashTable *externally_removed_objs_hash = user_data;
return !g_hash_table_contains (externally_removed_objs_hash, obj);
}
/*****************************************************************************/
static void
_load_link (NML3Cfg *self, gboolean initial)
{
@ -131,6 +340,26 @@ _nm_l3cfg_notify_platform_change_on_idle (NML3Cfg *self, guint32 obj_type_flags)
_property_emit_notify (self, NM_L3CFG_PROPERTY_EMIT_TYPE_IP6_ROUTE);
}
void
_nm_l3cfg_notify_platform_change (NML3Cfg *self,
NMPlatformSignalChangeType change_type,
const NMPObject *obj)
{
nm_assert (NMP_OBJECT_IS_VALID (obj));
switch (NMP_OBJECT_GET_TYPE (obj)) {
case NMP_OBJECT_TYPE_IP4_ADDRESS:
case NMP_OBJECT_TYPE_IP6_ADDRESS:
case NMP_OBJECT_TYPE_IP4_ROUTE:
case NMP_OBJECT_TYPE_IP6_ROUTE:
_l3cfg_externally_removed_objs_track (self,
obj,
change_type == NM_PLATFORM_SIGNAL_REMOVED);
default:
break;
}
}
/*****************************************************************************/
typedef struct {
@ -775,26 +1004,37 @@ out_prune:
gboolean
nm_l3cfg_platform_commit (NML3Cfg *self,
NML3CfgCommitType commit_type,
int addr_family,
gboolean *out_final_failure_for_temporary_not_available)
{
nm_auto_unref_l3cfg const NML3ConfigData *l3cfg_old = NULL;
gs_unref_ptrarray GPtrArray *addresses = NULL;
gs_unref_ptrarray GPtrArray *routes = NULL;
gs_unref_ptrarray GPtrArray *addresses_prune = NULL;
gs_unref_ptrarray GPtrArray *routes_prune = NULL;
gs_unref_ptrarray GPtrArray *routes_temporary_not_available_arr = NULL;
NMIPRouteTableSyncMode route_table_sync = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE;
gboolean final_failure_for_temporary_not_available = FALSE;
char sbuf_commit_type[50];
gboolean combined_changed;
gboolean success = TRUE;
int IS_IPv4;
g_return_val_if_fail (NM_IS_L3CFG (self), FALSE);
nm_assert (NM_IN_SET (commit_type, NM_L3_CFG_COMMIT_TYPE_REAPPLY,
NM_L3_CFG_COMMIT_TYPE_UPDATE,
NM_L3_CFG_COMMIT_TYPE_ASSUME));
if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY)
_l3cfg_externally_removed_objs_drop (self, addr_family);
if (addr_family == AF_UNSPEC) {
gboolean final_failure_for_temporary_not_available_6 = FALSE;
if (!nm_l3cfg_platform_commit (self, AF_INET, &final_failure_for_temporary_not_available))
if (!nm_l3cfg_platform_commit (self, AF_INET, commit_type, &final_failure_for_temporary_not_available))
success = FALSE;
if (!nm_l3cfg_platform_commit (self, AF_INET6, &final_failure_for_temporary_not_available_6))
if (!nm_l3cfg_platform_commit (self, AF_INET6, commit_type, &final_failure_for_temporary_not_available_6))
success = FALSE;
NM_SET_OUT (out_final_failure_for_temporary_not_available,
( final_failure_for_temporary_not_available
@ -802,20 +1042,47 @@ nm_l3cfg_platform_commit (NML3Cfg *self,
return success;
}
_l3cfg_update_combined_config (self, NULL);
_LOGT ("committing IPv%c configuration (%s)",
nm_utils_addr_family_to_char (addr_family),
_l3_cfg_commit_type_to_string (commit_type, sbuf_commit_type, sizeof (sbuf_commit_type)));
combined_changed = _l3cfg_update_combined_config (self, &l3cfg_old);
IS_IPv4 = NM_IS_IPv4 (addr_family);
_LOGT ("committing IPv%c configuration...", nm_utils_addr_family_to_char (addr_family));
if (combined_changed) {
/* our combined configuration changed. We may track entries in externally_removed_objs_hash,
* which are not longer to be considered by our configuration. We need to forget about them. */
_l3cfg_externally_removed_objs_drop_unused (self);
}
if (commit_type == NM_L3_CFG_COMMIT_TYPE_ASSUME) {
/* we need to artificially pre-populate the externally remove hash. */
_l3cfg_externally_removed_objs_pickup (self, addr_family);
}
if (self->priv.p->combined_l3cfg) {
NMDedupMultiFcnSelectPredicate predicate;
if ( commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY
&& self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] > 0)
predicate = _l3cfg_externally_removed_objs_filter;
else
predicate = NULL;
addresses = nm_dedup_multi_objs_to_ptr_array_head (nm_l3_config_data_lookup_objs (self->priv.p->combined_l3cfg,
NMP_OBJECT_TYPE_IP_ADDRESS (IS_IPv4)),
NULL, NULL);
predicate,
self->priv.p->externally_removed_objs_hash);
if ( commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY
&& self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] > 0)
predicate = _l3cfg_externally_removed_objs_filter;
else
predicate = NULL;
routes = nm_dedup_multi_objs_to_ptr_array_head (nm_l3_config_data_lookup_objs (self->priv.p->combined_l3cfg,
NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4)),
NULL, NULL);
predicate,
self->priv.p->externally_removed_objs_hash);
route_table_sync = nm_l3_config_data_get_route_table_sync (self->priv.p->combined_l3cfg, addr_family);
}
@ -823,14 +1090,42 @@ nm_l3cfg_platform_commit (NML3Cfg *self,
if (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_NONE)
route_table_sync = NM_IP_ROUTE_TABLE_SYNC_MODE_ALL;
routes_prune = nm_platform_ip_route_get_prune_list (self->priv.platform,
addr_family,
self->priv.ifindex,
route_table_sync);
if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY) {
addresses_prune = nm_platform_ip_address_get_prune_list (self->priv.platform,
addr_family,
self->priv.ifindex,
TRUE);
routes_prune = nm_platform_ip_route_get_prune_list (self->priv.platform,
addr_family,
self->priv.ifindex,
route_table_sync);
} else if (commit_type == NM_L3_CFG_COMMIT_TYPE_UPDATE) {
/* during update, we do a cross with the previous configuration.
*
* Of course, if an entry is both to be pruned and to be added, then
* the latter wins. So, this works just nicely. */
if (l3cfg_old) {
const NMDedupMultiHeadEntry *head_entry;
nm_platform_ip4_address_sync (self->priv.platform,
self->priv.ifindex,
addresses);
head_entry = nm_l3_config_data_lookup_objs (l3cfg_old,
NMP_OBJECT_TYPE_IP_ADDRESS (IS_IPv4));
addresses_prune = nm_dedup_multi_objs_to_ptr_array_head (head_entry,
NULL,
NULL);
head_entry = nm_l3_config_data_lookup_objs (l3cfg_old,
NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4));
addresses_prune = nm_dedup_multi_objs_to_ptr_array_head (head_entry,
NULL,
NULL);
}
}
nm_platform_ip_address_sync (self->priv.platform,
addr_family,
self->priv.ifindex,
addresses,
addresses_prune);
if (!nm_platform_ip_route_sync (self->priv.platform,
addr_family,
@ -930,6 +1225,8 @@ finalize (GObject *object)
nm_clear_g_source (&self->priv.p->routes_temporary_not_available_id);
nm_clear_pointer (&self->priv.p->routes_temporary_not_available_hash, g_hash_table_unref);
nm_clear_pointer (&self->priv.p->externally_removed_objs_hash, g_hash_table_unref);
g_clear_object (&self->priv.netns);
g_clear_object (&self->priv.platform);

View file

@ -49,6 +49,10 @@ NML3Cfg *nm_l3cfg_new (NMNetns *netns, int ifindex);
void _nm_l3cfg_notify_platform_change_on_idle (NML3Cfg *self, guint32 obj_type_flags);
void _nm_l3cfg_notify_platform_change (NML3Cfg *self,
NMPlatformSignalChangeType change_type,
const NMPObject *obj);
/*****************************************************************************/
static inline int
@ -125,7 +129,26 @@ void nm_l3cfg_remove_config_all (NML3Cfg *self,
/*****************************************************************************/
typedef enum {
/* ASSUME means to keep any pre-existing extra routes/addresses, while
* also not adding routes/addresses that are not present yet. This is to
* gracefully take over after restart, where the existing IP configuration
* should not change. */
NM_L3_CFG_COMMIT_TYPE_ASSUME,
/* UPDATE means to add new addresses/routes, while also removing addresses/routes
* that are no longer present (but were previously configured by NetworkManager).
* Routes/addresses that were removed externally won't be re-added, and routes/addresses
* that are added externally won't be removed. */
NM_L3_CFG_COMMIT_TYPE_UPDATE,
/* This is a full sync. It configures the IP addresses/routes that are indicated,
* while removing the existing ones from the interface. */
NM_L3_CFG_COMMIT_TYPE_REAPPLY,
} NML3CfgCommitType;
gboolean nm_l3cfg_platform_commit (NML3Cfg *self,
NML3CfgCommitType commit_type,
int addr_family,
gboolean *out_final_failure_for_temporary_not_available);

View file

@ -196,6 +196,7 @@ _platform_signal_cb (NMPlatform *platform,
NMNetns *self = NM_NETNS (*p_self);
NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE (self);
const NMPObjectType obj_type = obj_type_i;
const NMPlatformSignalChangeType change_type = change_type_i;
L3CfgData *l3cfg_data;
l3cfg_data = g_hash_table_lookup (priv->l3cfgs, &ifindex);
@ -204,12 +205,15 @@ _platform_signal_cb (NMPlatform *platform,
l3cfg_data->signal_pending_flag |= nmp_object_type_to_flags (obj_type);
if (!c_list_is_empty (&l3cfg_data->signal_pending_lst))
return;
if (c_list_is_empty (&l3cfg_data->signal_pending_lst)) {
c_list_link_tail (&priv->l3cfg_signal_pending_lst_head, &l3cfg_data->signal_pending_lst);
if (priv->signal_pending_idle_id == 0)
priv->signal_pending_idle_id = g_idle_add (_platform_signal_on_idle_cb, self);
}
c_list_link_tail (&priv->l3cfg_signal_pending_lst_head, &l3cfg_data->signal_pending_lst);
if (priv->signal_pending_idle_id == 0)
priv->signal_pending_idle_id = g_idle_add (_platform_signal_on_idle_cb, self);
_nm_l3cfg_notify_platform_change (l3cfg_data->l3cfg,
change_type,
NMP_OBJECT_UP_CAST (platform_object));
}
/*****************************************************************************/
@ -288,6 +292,8 @@ constructed (GObject *object)
g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (_platform_signal_cb), &priv->_self_signal_user_data);
g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_platform_signal_cb), &priv->_self_signal_user_data);
g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_platform_signal_cb), &priv->_self_signal_user_data);
g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (_platform_signal_cb), &priv->_self_signal_user_data);
g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (_platform_signal_cb), &priv->_self_signal_user_data);
}
NMNetns *