diff options
Diffstat (limited to 'src/core/nm-ip4-config.c')
| -rw-r--r-- | src/core/nm-ip4-config.c | 3320 |
1 files changed, 3320 insertions, 0 deletions
diff --git a/src/core/nm-ip4-config.c b/src/core/nm-ip4-config.c new file mode 100644 index 00000000..c49dfb4c --- /dev/null +++ b/src/core/nm-ip4-config.c @@ -0,0 +1,3320 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2005 - 2017 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-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 "nm-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; + bool never_default : 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 { + NMIPConfig parent; + NMIP4ConfigPrivate _priv; +}; + +struct _NMIP4ConfigClass { + NMIPConfigClass parent; +}; + +G_DEFINE_TYPE(NMIP4Config, nm_ip4_config, NM_TYPE_IP_CONFIG) + +#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; +} + +/*****************************************************************************/ + +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) +{ + nm_assert(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) +{ + nm_assert(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 || nmp_object_ip_route_is_best_defaut_route(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 && nmp_object_ip_route_is_best_defaut_route(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_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 nmp_object_ref_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 +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)); +} + +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; + + /* Pre-generate local route added by kernel */ + 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 = my_addr->address; + route->plen = 32; + route->pref_src = my_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); + nm_clear_pointer(&r, nmp_object_unref); + + if (nm_utils_ip4_address_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_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_utils_ip_route_attribute_to_platform(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); + + nm_ip4_config_set_never_default(self, nm_setting_ip_config_get_never_default(setting)); + + 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)); + + /* never default */ + nm_ip4_config_set_never_default(dst, + nm_ip4_config_get_never_default(dst) + || nm_ip4_config_get_never_default(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) { + nmp_object_ref_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 (nmp_object_ref_set(&dst_priv->best_default_route, new_best_default_route)) { + nm_assert(changed); + _notify(dst, PROP_GATEWAY); + } + + if (changed) { + _notify_routes(dst); + result = TRUE; + } + +skip_routes: + /* ignore domains */ + /* ignore dns searches */ + /* ignore dns options */ + /* 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_platform_dedup_multi_iter_next_ip4_address(&ipconf_iter_src, &r_src); + if (has != nm_platform_dedup_multi_iter_next_ip4_address(&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_platform_dedup_multi_iter_next_ip4_route(&ipconf_iter_src, &r_src); + if (has != nm_platform_dedup_multi_iter_next_ip4_route(&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 (nmp_object_ref_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; + } + + /* never default */ + if (src_priv->never_default != dst_priv->never_default) { + dst_priv->never_default = src_priv->never_default; + 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; +} + +/*****************************************************************************/ + +void +nm_ip4_config_set_never_default(NMIP4Config *self, gboolean never_default) +{ + NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + priv->never_default = never_default; +} + +gboolean +nm_ip4_config_get_never_default(const NMIP4Config *self) +{ + const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE(self); + + return priv->never_default; +} + +/*****************************************************************************/ + +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 (nmp_object_ref_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); + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + GVariantBuilder builder_data; + guint i; + + switch (prop_id) { + case PROP_IFINDEX: + g_value_set_int(value, priv->ifindex); + break; + case PROP_ADDRESS_DATA: + case PROP_ADDRESSES: + nm_assert(!!priv->address_data_variant == !!priv->addresses_variant); + + if (!priv->address_data_variant) { + nm_utils_ip_addresses_to_dbus(AF_INET, + nm_ip4_config_lookup_addresses(self), + priv->best_default_route, + NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN, + &priv->address_data_variant, + &priv->addresses_variant); + g_variant_ref_sink(priv->address_data_variant); + g_variant_ref_sink(priv->addresses_variant); + } + + g_value_set_variant(value, + prop_id == PROP_ADDRESS_DATA ? priv->address_data_variant + : priv->addresses_variant); + break; + case PROP_ROUTE_DATA: + case PROP_ROUTES: + nm_assert(!!priv->route_data_variant == !!priv->routes_variant); + + if (!priv->route_data_variant) { + nm_utils_ip_routes_to_dbus(AF_INET, + nm_ip4_config_lookup_routes(self), + &priv->route_data_variant, + &priv->routes_variant); + g_variant_ref_sink(priv->route_data_variant); + g_variant_ref_sink(priv->routes_variant); + } + + g_value_set_variant(value, + prop_id == PROP_ROUTE_DATA ? priv->route_data_variant + : priv->routes_variant); + break; + case PROP_GATEWAY: + if (priv->best_default_route) { + g_value_take_string(value, + nm_utils_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 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 *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMIPConfigClass * ip_config_class = NM_IP_CONFIG_CLASS(klass); + + ip_config_class->is_ipv4 = TRUE; + ip_config_class->addr_family = AF_INET; + + 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); +} |