diff options
author | Thomas Haller <thaller@redhat.com> | 2020-07-31 08:56:58 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2020-07-31 08:56:58 +0200 |
commit | e95b400d0408f0cdae80e91598eb38dbd90f5383 (patch) | |
tree | d676b0fd396740cb2e3b3d47b936585b47d2a8d4 | |
parent | a938f4f018f6fc9b35297b2320c5e295742ab087 (diff) | |
parent | e50597559b22ab5a16ab00924f7aba178d1c95f8 (diff) | |
download | NetworkManager-e95b400d0408f0cdae80e91598eb38dbd90f5383.tar.gz |
l3cfg: merge branch 'th/l3cfg-3'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/591
-rw-r--r-- | libnm-core/tests/test-general.c | 13 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-dedup-multi.c | 7 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-macros-internal.h | 10 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-shared-utils.h | 26 | ||||
-rw-r--r-- | shared/nm-std-aux/nm-std-aux.h | 21 | ||||
-rw-r--r-- | src/devices/nm-device.c | 5 | ||||
-rw-r--r-- | src/nm-core-utils.h | 29 | ||||
-rw-r--r-- | src/nm-ip4-config.c | 49 | ||||
-rw-r--r-- | src/nm-ip6-config.c | 72 | ||||
-rw-r--r-- | src/nm-l3-config-data.c | 1008 | ||||
-rw-r--r-- | src/nm-l3-config-data.h | 205 | ||||
-rw-r--r-- | src/nm-l3cfg.c | 941 | ||||
-rw-r--r-- | src/nm-l3cfg.h | 67 | ||||
-rw-r--r-- | src/nm-netns.c | 16 | ||||
-rw-r--r-- | src/nm-types.h | 7 | ||||
-rw-r--r-- | src/platform/nm-platform.c | 574 | ||||
-rw-r--r-- | src/platform/nm-platform.h | 50 | ||||
-rw-r--r-- | src/platform/nmp-object.c | 22 | ||||
-rw-r--r-- | src/platform/nmp-object.h | 20 | ||||
-rw-r--r-- | src/platform/tests/test-platform-general.c | 8 |
20 files changed, 2617 insertions, 533 deletions
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c index 1e4efe2452..73d68f8a62 100644 --- a/libnm-core/tests/test-general.c +++ b/libnm-core/tests/test-general.c @@ -7290,8 +7290,16 @@ enum TEST_IS_POWER_OF_TWP_ENUM_UNSIGNED_64 { G_STMT_START { \ typeof (x) x1 = (x); \ type x2 = (type) x1; \ + gboolean val; \ \ - g_assert_cmpint (expect, ==, nm_utils_is_power_of_two (x1)); \ + val = nm_utils_is_power_of_two (x1); \ + g_assert_cmpint (expect, ==, val); \ + if (x1 != 0) \ + g_assert_cmpint (val, ==, nm_utils_is_power_of_two_or_zero (x1)); \ + else { \ + g_assert (nm_utils_is_power_of_two_or_zero (x1)); \ + g_assert (!val); \ + } \ if ( ((typeof (x1)) x2) == x1 \ && ((typeof (x2)) x1) == x2 \ && x2 > 0) { \ @@ -7318,6 +7326,9 @@ test_nm_utils_is_power_of_two (void) GRand *rand = nmtst_get_rand (); int numbits; + g_assert (!nm_utils_is_power_of_two (0)); + g_assert (nm_utils_is_power_of_two_or_zero (0)); + for (i = -1; i < 64; i++) { /* find a (positive) x which is a power of two. */ diff --git a/shared/nm-glib-aux/nm-dedup-multi.c b/shared/nm-glib-aux/nm-dedup-multi.c index 6e23228e37..4e61fd4143 100644 --- a/shared/nm-glib-aux/nm-dedup-multi.c +++ b/shared/nm-glib-aux/nm-dedup-multi.c @@ -54,9 +54,10 @@ nm_dedup_multi_idx_type_init (NMDedupMultiIdxType *idx_type, nm_assert (idx_type); nm_assert (klass); - memset (idx_type, 0, sizeof (*idx_type)); - idx_type->klass = klass; - c_list_init (&idx_type->lst_idx_head); + *idx_type = (NMDedupMultiIdxType) { + .klass = klass, + .lst_idx_head = C_LIST_INIT (idx_type->lst_idx_head), + }; ASSERT_idx_type (idx_type); } diff --git a/shared/nm-glib-aux/nm-macros-internal.h b/shared/nm-glib-aux/nm-macros-internal.h index a8a5360548..551cd92f16 100644 --- a/shared/nm-glib-aux/nm-macros-internal.h +++ b/shared/nm-glib-aux/nm-macros-internal.h @@ -1017,16 +1017,6 @@ nm_g_variant_take_ref (GVariant *v) /*****************************************************************************/ -/* Determine whether @x is a power of two (@x being an integer type). - * Basically, this returns TRUE, if @x has exactly one bit set. - * For negative values and zero, this always returns FALSE. */ -#define nm_utils_is_power_of_two(x) ({ \ - typeof(x) __x = (x); \ - \ - ( (__x > ((typeof(__x)) 0)) \ - && ((__x & (__x - (((typeof(__x)) 1)))) == ((typeof(__x)) 0))); \ - }) - #define NM_DIV_ROUND_UP(x, y) \ ({ \ const typeof(x) _x = (x); \ diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h index 2f6681cb70..d44321d34e 100644 --- a/shared/nm-glib-aux/nm-shared-utils.h +++ b/shared/nm-glib-aux/nm-shared-utils.h @@ -262,6 +262,12 @@ gboolean nm_utils_ipaddr_is_normalized (int addr_family, return (_a < _b) ? -1 : 1; \ } G_STMT_END +#define NM_CMP_DIRECT_UNSAFE(a, b) \ + G_STMT_START { \ + if ((a) != (b)) \ + return ((a) < (b)) ? -1 : 1; \ + } G_STMT_END + /* In the general case, direct pointer comparison is undefined behavior in C. * Avoid that by casting pointers to void* and then to uintptr_t. This comparison * is not really meaningful, except that it provides some kind of stable sort order @@ -1547,6 +1553,12 @@ nm_g_ptr_array_len (const GPtrArray *arr) return arr ? arr->len : 0u; } +static inline gpointer * +nm_g_ptr_array_pdata (const GPtrArray *arr) +{ + return arr ? arr->pdata : NULL; +} + GPtrArray *_nm_g_ptr_array_copy (GPtrArray *array, GCopyFunc func, gpointer user_data, @@ -1873,6 +1885,20 @@ nm_strv_ptrarray_contains (const GPtrArray *strv, return nm_strv_ptrarray_find_first (strv, str) >= 0; } +static inline int +nm_strv_ptrarray_cmp (const GPtrArray *a, + const GPtrArray *b) +{ + /* _nm_utils_strv_cmp_n() will treat NULL and empty arrays the same. + * That means, an empty strv array can both be represented by NULL + * and an array of length zero. + * If you need to distinguish between these case, do that yourself. */ + return _nm_utils_strv_cmp_n ((const char *const*) nm_g_ptr_array_pdata (a), + nm_g_ptr_array_len (a), + (const char *const*) nm_g_ptr_array_pdata (b), + nm_g_ptr_array_len (b)); +} + /*****************************************************************************/ int nm_utils_getpagesize (void); diff --git a/shared/nm-std-aux/nm-std-aux.h b/shared/nm-std-aux/nm-std-aux.h index 01409cefbb..b3f80bbd65 100644 --- a/shared/nm-std-aux/nm-std-aux.h +++ b/shared/nm-std-aux/nm-std-aux.h @@ -193,6 +193,27 @@ ((_A) > (_B)) ? (_A) : (_B), \ ((void) 0))) +/* Determine whether @x is a power of two (@x being an integer type). + * Basically, this returns TRUE, if @x has exactly one bit set. + * For negative values and zero, this always returns FALSE. */ +#define nm_utils_is_power_of_two(x) \ + ({ \ + typeof(x) _x2 = (x); \ + const typeof(_x2) _X_0 = ((typeof(_x2)) 0); \ + const typeof(_x2) _X_1 = ((typeof(_x2)) 1); \ + \ + ( (_x2 > _X_0) \ + && ((_x2 & (_x2 - _X_1)) == _X_0)); \ + }) + +#define nm_utils_is_power_of_two_or_zero(x) \ + ({ \ + typeof (x) _x1 = (x); \ + \ + ( (_x1 == 0) \ + || nm_utils_is_power_of_two (_x1)); \ + }) + /*****************************************************************************/ #define NM_SWAP(a, b) \ diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 02b1c548eb..03a1a59db6 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -7747,9 +7747,6 @@ ipv4ll_get_ip4_config (NMDevice *self, guint32 lla) return config; } -#define IPV4LL_NETWORK (htonl (0xA9FE0000L)) -#define IPV4LL_NETMASK (htonl (0xFFFF0000L)) - static void nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data) { @@ -7774,7 +7771,7 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data) return; } - if ((address.s_addr & IPV4LL_NETMASK) != IPV4LL_NETWORK) { + if (!nm_utils_ip4_address_is_link_local (address.s_addr)) { _LOGE (LOGD_AUTOIP4, "invalid address %08x received (not link-local).", address.s_addr); nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_ERROR); return; diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h index 5d36bd593c..8b5e69916d 100644 --- a/src/nm-core-utils.h +++ b/src/nm-core-utils.h @@ -113,6 +113,22 @@ nm_utils_ip4_address_same_prefix_cmp (in_addr_t addr_a, in_addr_t addr_b, guint8 int nm_utils_ip6_address_same_prefix_cmp (const struct in6_addr *addr_a, const struct in6_addr *addr_b, guint8 plen); +static inline int +nm_utils_ip_address_same_prefix_cmp (int addr_family, gconstpointer addr_a, gconstpointer addr_b, guint8 plen) +{ + nm_assert_addr_family (addr_family); + + NM_CMP_SELF (addr_a, addr_b); + + if (NM_IS_IPv4 (addr_family)) { + return nm_utils_ip4_address_same_prefix_cmp (*((const in_addr_t *) addr_a), + *((const in_addr_t *) addr_b), + plen); + } + + return nm_utils_ip6_address_same_prefix_cmp (addr_a, addr_b, plen); +} + static inline gboolean nm_utils_ip4_address_same_prefix (in_addr_t addr_a, in_addr_t addr_b, guint8 plen) { @@ -125,6 +141,12 @@ nm_utils_ip6_address_same_prefix (const struct in6_addr *addr_a, const struct in return nm_utils_ip6_address_same_prefix_cmp (addr_a, addr_b, plen) == 0; } +static inline gboolean +nm_utils_ip_address_same_prefix (int addr_family, gconstpointer addr_a, gconstpointer addr_b, guint8 plen) +{ + return nm_utils_ip_address_same_prefix_cmp (addr_family, addr_a, addr_b, plen) == 0; +} + #define NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a, b, plen) \ NM_CMP_RETURN (nm_utils_ip4_address_same_prefix_cmp ((a), (b), (plen))) @@ -457,6 +479,13 @@ nm_utils_ip4_address_is_link_local (in_addr_t addr) return (addr & NM_IPV4LL_NETMASK) == NM_IPV4LL_NETWORK; } +static inline gboolean +nm_utils_ip4_address_is_zeronet (in_addr_t network) +{ + /* Same as ipv4_is_zeronet() from kernel's include/linux/in.h. */ + return (network & htonl (0xFF000000u)) == htonl (0x00000000u); +} + /*****************************************************************************/ const char *nm_utils_dnsmasq_status_to_string (int status, char *dest, gsize size); diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c index 4a9026cdb3..a31b24c84f 100644 --- a/src/nm-ip4-config.c +++ b/src/nm-ip4-config.c @@ -343,15 +343,6 @@ nm_ip4_config_get_multi_idx (const NMIP4Config *self) /*****************************************************************************/ -static gboolean -_ipv4_is_zeronet (in_addr_t network) -{ - /* Same as ipv4_is_zeronet() from kernel's include/linux/in.h. */ - return (network & htonl(0xff000000)) == htonl(0x00000000); -} - -/*****************************************************************************/ - const NMDedupMultiHeadEntry * nm_ip4_config_lookup_addresses (const NMIP4Config *self) { @@ -623,30 +614,6 @@ nm_ip4_config_update_routes_metric (NMIP4Config *self, gint64 metric) g_object_thaw_notify (G_OBJECT (self)); } -static void -_add_local_route_from_addr4 (NMIP4Config * self, - const NMPlatformIP4Address *addr, - int ifindex, - guint32 route_table, - gboolean is_vrf) -{ - nm_auto_nmpobj NMPObject *r = NULL; - NMPlatformIP4Route *route; - - r = nmp_object_new (NMP_OBJECT_TYPE_IP4_ROUTE, NULL); - route = NMP_OBJECT_CAST_IP4_ROUTE (r); - route->ifindex = ifindex; - route->rt_source = NM_IP_CONFIG_SOURCE_KERNEL; - route->network = addr->address; - route->plen = 32; - route->pref_src = addr->address; - route->type_coerced = nm_platform_route_type_coerce (RTN_LOCAL); - route->scope_inv = nm_platform_route_scope_inv (RT_SCOPE_HOST); - route->table_coerced = nm_platform_route_table_coerce (is_vrf ? route_table : RT_TABLE_LOCAL); - - _add_route (self, r, NULL, NULL); -} - void nm_ip4_config_add_dependent_routes (NMIP4Config *self, guint32 route_table, @@ -684,9 +651,21 @@ nm_ip4_config_add_dependent_routes (NMIP4Config *self, if (my_addr->external) continue; - _add_local_route_from_addr4 (self, my_addr, ifindex, route_table, is_vrf); + /* 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 (_ipv4_is_zeronet (network)) { + 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; diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c index 00865734c5..cf7aa59f0d 100644 --- a/src/nm-ip6-config.c +++ b/src/nm-ip6-config.c @@ -364,46 +364,6 @@ nm_ip6_config_update_routes_metric (NMIP6Config *self, gint64 metric) g_object_thaw_notify (G_OBJECT (self)); } -static void -_add_multicast_route6 (NMIP6Config *self, int ifindex) -{ - nm_auto_nmpobj NMPObject *r = NULL; - NMPlatformIP6Route *route; - - r = nmp_object_new (NMP_OBJECT_TYPE_IP6_ROUTE, NULL); - route = NMP_OBJECT_CAST_IP6_ROUTE (r); - route->ifindex = ifindex; - route->network.s6_addr[0] = 0xffu; - route->plen = 8; - route->table_coerced = nm_platform_route_table_coerce (RT_TABLE_LOCAL); - route->type_coerced = nm_platform_route_type_coerce (RTN_UNICAST); - route->metric = 256; - - _add_route (self, r, NULL, NULL); -} - -static void -_add_local_route_from_addr6 (NMIP6Config * self, - const NMPlatformIP6Address *addr, - int ifindex, - guint32 route_table, - gboolean is_vrf) -{ - nm_auto_nmpobj NMPObject *r = NULL; - NMPlatformIP6Route * route; - - r = nmp_object_new (NMP_OBJECT_TYPE_IP6_ROUTE, NULL); - route = NMP_OBJECT_CAST_IP6_ROUTE (r); - route->ifindex = ifindex; - route->network = addr->address; - route->plen = 128; - route->type_coerced = nm_platform_route_type_coerce (RTN_LOCAL); - route->metric = 0; - route->table_coerced = nm_platform_route_table_coerce (is_vrf ? route_table : RT_TABLE_LOCAL); - - _add_route (self, r, NULL, NULL); -} - void nm_ip6_config_add_dependent_routes (NMIP6Config *self, guint32 route_table, @@ -426,7 +386,21 @@ nm_ip6_config_add_dependent_routes (NMIP6Config *self, * For manually added IPv6 routes, add the device routes explicitly. */ /* Pre-generate multicast route */ - _add_multicast_route6 (self, ifindex); + { + nm_auto_nmpobj NMPObject *r = NULL; + NMPlatformIP6Route *route; + + r = nmp_object_new (NMP_OBJECT_TYPE_IP6_ROUTE, NULL); + route = NMP_OBJECT_CAST_IP6_ROUTE (r); + route->ifindex = ifindex; + route->network.s6_addr[0] = 0xffu; + route->plen = 8; + route->table_coerced = nm_platform_route_table_coerce (RT_TABLE_LOCAL); + route->type_coerced = nm_platform_route_type_coerce (RTN_UNICAST); + route->metric = 256; + + _add_route (self, r, NULL, NULL); + } nm_ip_config_iter_ip6_address_for_each (&iter, self, &my_addr) { NMPlatformIP6Route *route; @@ -436,8 +410,20 @@ nm_ip6_config_add_dependent_routes (NMIP6Config *self, if (my_addr->external) continue; - /* Pre-generate local route added by kernel */ - _add_local_route_from_addr6 (self, my_addr, ifindex, route_table, is_vrf); + { + nm_auto_nmpobj NMPObject *r = NULL; + + /* Pre-generate local route added by kernel */ + r = nmp_object_new (NMP_OBJECT_TYPE_IP6_ROUTE, NULL); + route = NMP_OBJECT_CAST_IP6_ROUTE (r); + route->ifindex = ifindex; + route->network = my_addr->address; + route->plen = 128; + route->type_coerced = nm_platform_route_type_coerce (RTN_LOCAL); + route->metric = 0; + route->table_coerced = nm_platform_route_table_coerce (is_vrf ? route_table : RT_TABLE_LOCAL); + _add_route (self, r, NULL, NULL); + } if (NM_FLAGS_HAS (my_addr->n_ifa_flags, IFA_F_NOPREFIXROUTE)) continue; diff --git a/src/nm-l3-config-data.c b/src/nm-l3-config-data.c index 23d2dd0fae..f384ae2343 100644 --- a/src/nm-l3-config-data.c +++ b/src/nm-l3-config-data.c @@ -6,6 +6,7 @@ #include <linux/if.h> #include <linux/if_addr.h> +#include <linux/rtnetlink.h> #include "nm-core-internal.h" #include "platform/nm-platform.h" @@ -46,7 +47,10 @@ struct _NML3ConfigData { const NMPObject *best_default_route_x[2]; }; - GArray *wins_4; + GArray *wins; + GArray *nis_servers; + + char *nis_domain; union { struct { @@ -80,6 +84,10 @@ struct _NML3ConfigData { GPtrArray *dns_options_x[2]; }; + int ifindex; + + int ref_count; + union { struct { int dns_priority_6; @@ -88,14 +96,22 @@ struct _NML3ConfigData { int dns_priority_x[2]; }; + union { + struct { + NMIPRouteTableSyncMode route_table_sync_6; + NMIPRouteTableSyncMode route_table_sync_4; + }; + NMIPRouteTableSyncMode route_table_sync_x[2]; + }; + NMSettingConnectionMdns mdns; NMSettingConnectionLlmnr llmnr; - int ifindex; + NML3ConfigDatFlags flags; - int ref_count; + guint32 mtu; - NML3ConfigDatFlags flags; + NMTernary metered:3; bool is_sealed:1; }; @@ -118,7 +134,7 @@ _garray_inaddr_ensure (GArray **p_arr, } static GArray * -_garray_inaddr_clone (GArray *src, +_garray_inaddr_clone (const GArray *src, int addr_family) { const gsize elt_size = nm_utils_addr_family_to_size (addr_family); @@ -136,6 +152,44 @@ _garray_inaddr_clone (GArray *src, return dst; } +static void +_garray_inaddr_merge (GArray **p_dst, + const GArray *src, + int addr_family) +{ + guint dst_initial_len; + const char *p_dst_arr; + const char *p_src; + gsize elt_size; + guint i; + guint j; + + if (nm_g_array_len (src) == 0) + return; + + if (!*p_dst) { + *p_dst = _garray_inaddr_clone (src, addr_family); + return; + } + + elt_size = nm_utils_addr_family_to_size (addr_family); + + dst_initial_len = (*p_dst)->len; + p_dst_arr = (*p_dst)->data; + p_src = src->data; + + for (i = 0; i < src->len; i++, p_src += elt_size) { + for (j = 0; j < dst_initial_len; j++) { + if (memcmp (&p_dst_arr[j * elt_size], p_src, elt_size) == 0) + goto next; + } + g_array_append_vals (*p_dst, p_src, 1); + p_dst_arr = (*p_dst)->data; +next: + ; + } +} + static gssize _garray_inaddr_find (GArray *arr, int addr_family, @@ -183,6 +237,50 @@ _garray_inaddr_add (GArray **p_arr, return TRUE; } +static int +_garray_inaddr_cmp (const GArray *a, const GArray *b, int addr_family) +{ + guint l; + + l = nm_g_array_len (a); + NM_CMP_DIRECT (l, nm_g_array_len (b)); + + if (l > 0) + NM_CMP_DIRECT_MEMCMP (a->data, b->data, l * nm_utils_addr_family_to_size (addr_family)); + + return 0; +} + +static void +_strv_ptrarray_merge (GPtrArray **p_dst, const GPtrArray *src) +{ + guint dst_initial_len; + guint i; + + if (nm_g_ptr_array_len (src) == 0) + return; + + if (!*p_dst) { + /* we trust src to contain unique strings. Just clone it. */ + *p_dst = nm_strv_ptrarray_clone (src, TRUE); + return; + } + + nm_strv_ptrarray_ensure (p_dst); + + dst_initial_len = (*p_dst)->len; + + for (i = 0; i < src->len; i++) { + const char *s = src->pdata[i]; + + if ( dst_initial_len > 0 + && nm_utils_strv_find_first ((char **) ((*p_dst)->pdata), dst_initial_len, s) >= 0) + continue; + + g_ptr_array_add (*p_dst, g_strdup (s)); + } +} + /*****************************************************************************/ static gboolean @@ -216,7 +314,7 @@ _route_valid (int addr_family, gconstpointer r) } static gboolean -NM_IS_L3_CONFIG_DATA (const NML3ConfigData *self, gboolean allow_sealed) +_NM_IS_L3_CONFIG_DATA (const NML3ConfigData *self, gboolean allow_sealed) { nm_assert ( !self || ( self->ifindex > 0 @@ -269,12 +367,15 @@ nm_l3_config_data_new (NMDedupMultiIndex *multi_idx, self = g_slice_new (NML3ConfigData); *self = (NML3ConfigData) { - .ref_count = 1, - .ifindex = ifindex, - .multi_idx = nm_dedup_multi_index_ref (multi_idx), - .mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT, - .llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT, - .flags = NM_L3_CONFIG_DAT_FLAGS_NONE, + .ref_count = 1, + .ifindex = ifindex, + .multi_idx = nm_dedup_multi_index_ref (multi_idx), + .mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT, + .llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT, + .flags = NM_L3_CONFIG_DAT_FLAGS_NONE, + .metered = NM_TERNARY_DEFAULT, + .route_table_sync_4 = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE, + .route_table_sync_6 = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE, }; _idx_type_init (&self->idx_addresses_4, NMP_OBJECT_TYPE_IP4_ADDRESS); @@ -288,7 +389,7 @@ nm_l3_config_data_new (NMDedupMultiIndex *multi_idx, const NML3ConfigData * nm_l3_config_data_ref (const NML3ConfigData *self) { - nm_assert (NM_IS_L3_CONFIG_DATA (self, TRUE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); ((NML3ConfigData *) self)->ref_count++; return self; } @@ -296,7 +397,7 @@ nm_l3_config_data_ref (const NML3ConfigData *self) const NML3ConfigData * nm_l3_config_data_ref_and_seal (const NML3ConfigData *self) { - nm_assert (NM_IS_L3_CONFIG_DATA (self, TRUE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); ((NML3ConfigData *) self)->is_sealed = TRUE; ((NML3ConfigData *) self)->ref_count++; return self; @@ -305,7 +406,7 @@ nm_l3_config_data_ref_and_seal (const NML3ConfigData *self) const NML3ConfigData * nm_l3_config_data_seal (const NML3ConfigData *self) { - nm_assert (NM_IS_L3_CONFIG_DATA (self, TRUE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); ((NML3ConfigData *) self)->is_sealed = TRUE; return self; } @@ -313,7 +414,7 @@ nm_l3_config_data_seal (const NML3ConfigData *self) gboolean nm_l3_config_data_is_sealed (const NML3ConfigData *self) { - nm_assert (NM_IS_L3_CONFIG_DATA (self, TRUE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); return self->is_sealed; } @@ -325,7 +426,7 @@ nm_l3_config_data_unref (const NML3ConfigData *self) if (!self) return; - nm_assert (NM_IS_L3_CONFIG_DATA (self, TRUE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); /* NML3ConfigData aims to be an immutable, ref-counted type. The mode of operation * is to create/initialize the instance once, then seal it and pass around the reference. @@ -346,7 +447,8 @@ nm_l3_config_data_unref (const NML3ConfigData *self) nmp_object_unref (mutable->best_default_route_4); nmp_object_unref (mutable->best_default_route_6); - nm_clear_pointer (&mutable->wins_4, g_array_unref); + nm_clear_pointer (&mutable->wins, g_array_unref); + nm_clear_pointer (&mutable->nis_servers, g_array_unref); nm_clear_pointer (&mutable->nameservers_4, g_array_unref); nm_clear_pointer (&mutable->nameservers_6, g_array_unref); @@ -362,45 +464,128 @@ nm_l3_config_data_unref (const NML3ConfigData *self) nm_dedup_multi_index_unref (mutable->multi_idx); + g_free (mutable->nis_domain); + nm_g_slice_free (mutable); } /*****************************************************************************/ -const NMDedupMultiHeadEntry * -nm_l3_config_data_lookup_objs (const NML3ConfigData *self, NMPObjectType obj_type) +static const NMDedupMultiEntry * +_lookup_route (const NMDedupMultiIndex *multi_idx, + const DedupMultiIdxType *idx_type, + const NMPObject *needle) +{ + 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); + nm_assert ( !entry + || ( 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), NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 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), NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0)); + + return entry; +} + +const NMDedupMultiEntry * +nm_l3_config_data_lookup_route_obj (const NML3ConfigData *self, + const NMPObject *needle) { - const DedupMultiIdxType *idx; + gboolean IS_IPv4; - nm_assert (NM_IS_L3_CONFIG_DATA (self, TRUE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); + nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (needle), NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)); + + IS_IPv4 = NM_IS_IPv4 (NMP_OBJECT_GET_ADDR_FAMILY (needle)); + return _lookup_route (self->multi_idx, + &self->idx_routes_x[IS_IPv4], + needle); +} + +const NMDedupMultiEntry * +nm_l3_config_data_lookup_route (const NML3ConfigData *self, + int addr_family, + const NMPlatformIPRoute *needle) +{ + const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family); + NMPObject obj_stack; + + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); + nm_assert_addr_family (addr_family); + nm_assert (needle); + + return _lookup_route (self->multi_idx, + &self->idx_routes_x[IS_IPv4], + nmp_object_stackinit (&obj_stack, NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4), needle)); +} + +const NMDedupMultiIdxType * +nm_l3_config_data_lookup_index (const NML3ConfigData *self, NMPObjectType obj_type) +{ + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); switch (obj_type) { case NMP_OBJECT_TYPE_IP4_ADDRESS: - idx = &self->idx_addresses_4; - break; + return &self->idx_addresses_4.parent; case NMP_OBJECT_TYPE_IP6_ADDRESS: - idx = &self->idx_addresses_6; - break; + return &self->idx_addresses_6.parent; case NMP_OBJECT_TYPE_IP4_ROUTE: - idx = &self->idx_routes_4; - break; + return &self->idx_routes_4.parent; case NMP_OBJECT_TYPE_IP6_ROUTE: - idx = &self->idx_routes_6; - break; + return &self->idx_routes_6.parent; default: nm_assert_not_reached (); return NULL; } +} - return nm_dedup_multi_index_lookup_head (self->multi_idx, &idx->parent, NULL); +const NMDedupMultiHeadEntry * +nm_l3_config_data_lookup_objs (const NML3ConfigData *self, NMPObjectType obj_type) +{ + return nm_dedup_multi_index_lookup_head (self->multi_idx, + nm_l3_config_data_lookup_index (self, obj_type), + NULL); +} + +const NMDedupMultiEntry * +nm_l3_config_data_lookup_obj (const NML3ConfigData *self, + const NMPObject *obj) +{ + const NMDedupMultiIdxType *idx; + + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); + + idx = nm_l3_config_data_lookup_index (self, + NMP_OBJECT_GET_TYPE (obj)); + + return nm_dedup_multi_index_lookup_obj (self->multi_idx, + idx, + obj); } /*****************************************************************************/ +NMDedupMultiIndex * +nm_l3_config_data_get_multi_idx (const NML3ConfigData *self) +{ + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); + + return self->multi_idx; +} + int nm_l3_config_data_get_ifindex (const NML3ConfigData *self) { - nm_assert (NM_IS_L3_CONFIG_DATA (self, TRUE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); return self->ifindex; } @@ -410,7 +595,7 @@ nm_l3_config_data_get_ifindex (const NML3ConfigData *self) NML3ConfigDatFlags nm_l3_config_data_get_flags (const NML3ConfigData *self) { - nm_assert (NM_IS_L3_CONFIG_DATA (self, TRUE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); return self->flags; } @@ -420,8 +605,9 @@ nm_l3_config_data_set_flags_full (NML3ConfigData *self, NML3ConfigDatFlags flags, NML3ConfigDatFlags mask) { - nm_assert (NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); nm_assert (!NM_FLAGS_ANY (flags, ~mask)); + nm_assert (!NM_FLAGS_ANY (mask, ~NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES)); self->flags = (self->flags & ~mask) | (flags & mask); @@ -435,8 +621,7 @@ _l3_config_data_add_obj (NMDedupMultiIndex *multi_idx, int ifindex, const NMPObject *obj_new, const NMPlatformObject *pl_new, - gboolean merge, - gboolean append_force, + NML3ConfigAddFlags add_flags, const NMPObject **out_obj_old /* returns a reference! */, const NMPObject **out_obj_new /* does not return a reference */) { @@ -444,6 +629,8 @@ _l3_config_data_add_obj (NMDedupMultiIndex *multi_idx, const NMDedupMultiEntry *entry_old; const NMDedupMultiEntry *entry_new; + nm_assert (nm_utils_is_power_of_two_or_zero (add_flags & ( NM_L3_CONFIG_ADD_FLAGS_MERGE + | NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE))); nm_assert (multi_idx); nm_assert (idx_type); nm_assert (ifindex > 0); @@ -474,14 +661,17 @@ _l3_config_data_add_obj (NMDedupMultiIndex *multi_idx, gboolean modified = FALSE; const NMPObject *obj_old = entry_old->obj; + if (NM_FLAGS_HAS (add_flags, NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE)) { + nm_dedup_multi_entry_set_dirty (entry_old, FALSE); + goto append_force_and_out; + } + 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) { + if (NM_FLAGS_HAS (add_flags, NM_L3_CONFIG_ADD_FLAGS_MERGE)) { switch (idx_type->obj_type) { case NMP_OBJECT_TYPE_IP4_ADDRESS: case NMP_OBJECT_TYPE_IP6_ADDRESS: @@ -532,7 +722,7 @@ _l3_config_data_add_obj (NMDedupMultiIndex *multi_idx, if (!nm_dedup_multi_index_add_full (multi_idx, &idx_type->parent, obj_new, - append_force + NM_FLAGS_HAS (add_flags, NM_L3_CONFIG_ADD_FLAGS_APPEND_FORCE) ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE : NM_DEDUP_MULTI_IDX_MODE_APPEND, NULL, @@ -551,7 +741,7 @@ _l3_config_data_add_obj (NMDedupMultiIndex *multi_idx, 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_FLAGS_HAS (add_flags, NM_L3_CONFIG_ADD_FLAGS_APPEND_FORCE)) { if (nm_dedup_multi_entry_reorder (entry_old, NULL, TRUE)) return TRUE; } @@ -608,16 +798,17 @@ _l3_config_best_default_route_find_better (const NMPObject *obj_cur, const NMPOb } gboolean -nm_l3_config_data_add_address (NML3ConfigData *self, - int addr_family, - const NMPObject *obj_new, - const NMPlatformIPAddress *pl_new, - const NMPObject **out_obj_new) +nm_l3_config_data_add_address_full (NML3ConfigData *self, + int addr_family, + const NMPObject *obj_new, + const NMPlatformIPAddress *pl_new, + NML3ConfigAddFlags add_flags, + const NMPObject **out_obj_new) { const NMPObject *new; gboolean changed; - nm_assert (NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); nm_assert_addr_family (addr_family); nm_assert ((!pl_new) != (!obj_new)); nm_assert ( !obj_new @@ -630,8 +821,7 @@ nm_l3_config_data_add_address (NML3ConfigData *self, self->ifindex, obj_new, (const NMPlatformObject *) pl_new, - TRUE, - FALSE, + add_flags, NULL, &new); NM_SET_OUT (out_obj_new, nmp_object_ref (new)); @@ -647,12 +837,13 @@ _l3_config_best_default_route_merge (const NMPObject **best_default_route, const } gboolean -nm_l3_config_data_add_route (NML3ConfigData *self, - int addr_family, - const NMPObject *obj_new, - const NMPlatformIPRoute *pl_new, - const NMPObject **out_obj_new, - gboolean *out_changed_best_default_route) +nm_l3_config_data_add_route_full (NML3ConfigData *self, + int addr_family, + const NMPObject *obj_new, + const NMPlatformIPRoute *pl_new, + NML3ConfigAddFlags add_flags, + const NMPObject **out_obj_new, + gboolean *out_changed_best_default_route) { const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family); nm_auto_nmpobj const NMPObject *obj_old = NULL; @@ -660,7 +851,7 @@ nm_l3_config_data_add_route (NML3ConfigData *self, gboolean changed = FALSE; gboolean changed_best_default_route = FALSE; - nm_assert (NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); nm_assert_addr_family (addr_family); nm_assert ((!pl_new) != (!obj_new)); nm_assert ( !pl_new @@ -676,8 +867,7 @@ nm_l3_config_data_add_route (NML3ConfigData *self, self->ifindex, obj_new, (const NMPlatformObject *) pl_new, - TRUE, - FALSE, + add_flags, &obj_old, &obj_new_2)) { @@ -736,7 +926,7 @@ nm_l3_config_data_add_nameserver (NML3ConfigData *self, int addr_family, gconstpointer /* (const NMIPAddr *) */ nameserver) { - nm_assert (NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); nm_assert_addr_family (addr_family); nm_assert (nameserver); @@ -749,19 +939,39 @@ gboolean nm_l3_config_data_add_wins (NML3ConfigData *self, in_addr_t wins) { - nm_assert (NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); - return _garray_inaddr_add (&self->wins_4, + return _garray_inaddr_add (&self->wins, AF_INET, &wins); } gboolean +nm_l3_config_data_add_nis_server (NML3ConfigData *self, + in_addr_t nis_server) +{ + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); + + return _garray_inaddr_add (&self->nis_servers, + AF_INET, + &nis_server); +} + +gboolean +nm_l3_config_data_set_nis_domain (NML3ConfigData *self, + const char *nis_domain) +{ + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); + + return nm_utils_strdup_reset (&self->nis_domain, nis_domain); +} + +gboolean nm_l3_config_data_add_domain (NML3ConfigData *self, int addr_family, const char *domain) { - nm_assert (NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); nm_assert_addr_family (addr_family); return _check_and_add_domain (&self->domains_x[NM_IS_IPv4 (addr_family)], domain); @@ -772,7 +982,7 @@ nm_l3_config_data_add_search (NML3ConfigData *self, int addr_family, const char *search) { - nm_assert (NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); nm_assert_addr_family (addr_family); return _check_and_add_domain (&self->searches_x[NM_IS_IPv4 (addr_family)], search); @@ -785,7 +995,7 @@ nm_l3_config_data_add_dns_option (NML3ConfigData *self, { GPtrArray **p_arr; - nm_assert (NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); nm_assert_addr_family (addr_family); g_return_val_if_fail (dns_option, FALSE); @@ -808,19 +1018,471 @@ nm_l3_config_data_set_dns_priority (NML3ConfigData *self, int addr_family, int dns_priority) { - int *p_val; + const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family); + const NML3ConfigDatFlags has_dns_priority_flag = NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY (IS_IPv4); + + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert_addr_family (addr_family); + + if ( self->dns_priority_x[IS_IPv4] == dns_priority + && NM_FLAGS_ANY (self->flags, has_dns_priority_flag)) + return FALSE; + + self->flags |= has_dns_priority_flag; + self->dns_priority_x[IS_IPv4] = dns_priority; + return TRUE; +} + +gboolean +nm_l3_config_data_set_mdns (NML3ConfigData *self, + NMSettingConnectionMdns mdns) +{ + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); + + if (self->mdns == mdns) + return FALSE; + + self->mdns = mdns; + return TRUE; +} + +gboolean +nm_l3_config_data_set_llmnr (NML3ConfigData *self, + NMSettingConnectionLlmnr llmnr) +{ + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); + + if (self->llmnr == llmnr) + return FALSE; - nm_assert (NM_IS_L3_CONFIG_DATA (self, FALSE)); + self->llmnr = llmnr; + return TRUE; +} + +NMIPRouteTableSyncMode +nm_l3_config_data_get_route_table_sync (const NML3ConfigData *self, + int addr_family) +{ + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); nm_assert_addr_family (addr_family); - p_val = &self->dns_priority_x[NM_IS_IPv4 (addr_family)]; - if (*p_val == dns_priority) + return self->route_table_sync_x[NM_IS_IPv4 (addr_family)]; +} + +gboolean +nm_l3_config_data_set_route_table_sync (NML3ConfigData *self, + int addr_family, + NMIPRouteTableSyncMode route_table_sync) +{ + const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family); + + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert_addr_family (addr_family); + + if (self->route_table_sync_x[IS_IPv4] == route_table_sync) return FALSE; - *p_val = dns_priority; + self->route_table_sync_x [IS_IPv4] = route_table_sync; return TRUE; } +gboolean +nm_l3_config_data_set_metered (NML3ConfigData *self, + NMTernary metered) +{ + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); + + if (self->metered == metered) + return FALSE; + + self->metered = metered; + return TRUE; +} + +gboolean +nm_l3_config_data_set_mtu (NML3ConfigData *self, + guint32 mtu) +{ + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); + + if ( self->mtu == mtu + && NM_FLAGS_HAS (self->flags, NM_L3_CONFIG_DAT_FLAGS_HAS_MTU)) + return FALSE; + + self->mtu = mtu; + self->flags |= NM_L3_CONFIG_DAT_FLAGS_HAS_MTU; + return TRUE; +} + +/*****************************************************************************/ + +static int +_dedup_multi_index_cmp (const NML3ConfigData *a, + const NML3ConfigData *b, + NMPObjectType obj_type) +{ + const NMDedupMultiHeadEntry *h_a = nm_l3_config_data_lookup_objs (a, obj_type); + const NMDedupMultiHeadEntry *h_b = nm_l3_config_data_lookup_objs (b, obj_type); + NMDedupMultiIter iter_a; + NMDedupMultiIter iter_b; + + NM_CMP_SELF (h_a, h_b); + NM_CMP_DIRECT (h_a->len, h_b->len); + + nm_assert (h_a->len > 0); + + nm_dedup_multi_iter_init (&iter_a, h_a); + nm_dedup_multi_iter_init (&iter_b, h_b); + + while (TRUE) { + const NMPObject *obj_a; + const NMPObject *obj_b; + gboolean have_a; + gboolean have_b; + + have_a = nm_platform_dedup_multi_iter_next_obj (&iter_a, &obj_a, obj_type); + if (!have_a) { + nm_assert (!nm_platform_dedup_multi_iter_next_obj (&iter_b, &obj_b, obj_type)); + break; + } + + have_b = nm_platform_dedup_multi_iter_next_obj (&iter_b, &obj_b, obj_type); + nm_assert (have_b); + + NM_CMP_RETURN (nmp_object_cmp (obj_a, obj_b)); + } + + return 0; +} + +int +nm_l3_config_data_cmp (const NML3ConfigData *a, const NML3ConfigData *b) +{ + int IS_IPv4; + + NM_CMP_SELF (a, b); + + NM_CMP_DIRECT (a->ifindex, b->ifindex); + + NM_CMP_DIRECT (a->flags, b->flags); + + _dedup_multi_index_cmp (a, b, NMP_OBJECT_TYPE_IP4_ADDRESS); + _dedup_multi_index_cmp (a, b, NMP_OBJECT_TYPE_IP6_ADDRESS); + _dedup_multi_index_cmp (a, b, NMP_OBJECT_TYPE_IP4_ROUTE); + _dedup_multi_index_cmp (a, b, NMP_OBJECT_TYPE_IP6_ROUTE); + + for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { + const int addr_family = IS_IPv4 ? AF_INET : AF_INET6; + + NM_CMP_RETURN (nmp_object_cmp (a->best_default_route_x[IS_IPv4], b->best_default_route_x[IS_IPv4])); + + NM_CMP_RETURN (_garray_inaddr_cmp (a->nameservers_x[IS_IPv4], b->nameservers_x[IS_IPv4], addr_family)); + + NM_CMP_RETURN (nm_strv_ptrarray_cmp (a->domains_x[IS_IPv4], b->domains_x[IS_IPv4])); + NM_CMP_RETURN (nm_strv_ptrarray_cmp (a->searches_x[IS_IPv4], b->searches_x[IS_IPv4])); + NM_CMP_RETURN (nm_strv_ptrarray_cmp (a->dns_options_x[IS_IPv4], b->dns_options_x[IS_IPv4])); + + if (NM_FLAGS_ANY (a->flags, NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY (IS_IPv4))) + NM_CMP_DIRECT (a->dns_priority_x[IS_IPv4], b->dns_priority_x[IS_IPv4]); + + NM_CMP_DIRECT (a->route_table_sync_x[IS_IPv4], b->route_table_sync_x[IS_IPv4]); + } + + NM_CMP_RETURN (_garray_inaddr_cmp (a->wins, b->wins, AF_INET)); + NM_CMP_RETURN (_garray_inaddr_cmp (a->nis_servers, b->nis_servers, AF_INET)); + NM_CMP_FIELD_STR0 (a, b, nis_domain); + NM_CMP_DIRECT (a->mdns, b->mdns); + NM_CMP_DIRECT (a->llmnr, b->llmnr); + if (NM_FLAGS_HAS (a->flags, NM_L3_CONFIG_DAT_FLAGS_HAS_MTU)) + NM_CMP_DIRECT (a->mtu, b->mtu); + NM_CMP_DIRECT_UNSAFE (a->metered, b->metered); + + /* these fields are not considered by cmp(): + * + * - multi_idx + * - ref_count + * - is_sealed + */ + + return 0; +} + +/*****************************************************************************/ + +static const NMPObject * +_data_get_direct_route_for_host (const NML3ConfigData *self, + int addr_family, + gconstpointer host, + guint32 route_table) +{ + const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family); + const NMPObject *best_route_obj = NULL; + const NMPlatformIPXRoute *best_route = NULL; + const NMPObject *item_obj; + NMDedupMultiIter ipconf_iter; + + nm_assert (_NM_IS_L3_CONFIG_DATA (self, TRUE)); + nm_assert_addr_family (addr_family); + nm_assert (host); + + if (nm_ip_addr_is_null (addr_family, host)) + return NULL; + + nm_l3_config_data_iter_obj_for_each (ipconf_iter, self, item_obj, NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4)) { + const NMPlatformIPXRoute *item = NMP_OBJECT_CAST_IPX_ROUTE (item_obj); + + if (nm_ip_addr_is_null (addr_family, + nm_platform_ip_route_get_gateway (addr_family, &item->rx))) + continue; + + if ( best_route + && best_route->rx.plen > item->rx.plen) + continue; + + if (nm_platform_route_table_uncoerce (item->rx.table_coerced, TRUE) != route_table) + continue; + + if (!nm_utils_ip_address_same_prefix (addr_family, host, item->rx.network_ptr, item->rx.plen)) + continue; + + if ( best_route + && best_route->rx.metric <= item->rx.metric) + continue; + + best_route_obj = item_obj; + best_route = item; + } + return best_route_obj; +} + +/*****************************************************************************/ + +void +nm_l3_config_data_add_dependent_routes (NML3ConfigData *self, + int addr_family, + guint32 route_table, + guint32 route_metric, + gboolean is_vrf, + GPtrArray **out_ip4_dev_route_blacklist) +{ + const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family); + gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL; + gs_unref_ptrarray GPtrArray *extra_onlink_routes = NULL; + const NMPObject *my_addr_obj; + const NMPObject *my_route_obj; + NMPlatformIPXRoute rx; + NMDedupMultiIter iter; + in_addr_t network_4 = 0; + guint i; + + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert_addr_family (addr_family); + + /* For IPv6 slaac, we explicitly add the device-routes (onlink). + * As we don't do that for IPv4 and manual IPv6 addresses. Add them here + * as dependent routes. */ + + if (!IS_IPv4) { + /* Pre-generate multicast route */ + rx.r6 = (NMPlatformIP6Route) { + .ifindex = self->ifindex, + .network.s6_addr[0] = 0xffu, + .plen = 8, + .table_coerced = nm_platform_route_table_coerce (RT_TABLE_LOCAL), + .type_coerced = nm_platform_route_type_coerce (RTN_UNICAST), + .metric = 256, + }; + nm_l3_config_data_add_route (self, addr_family, NULL, &rx.rx); + } + + nm_l3_config_data_iter_obj_for_each (iter, self, my_addr_obj, NMP_OBJECT_TYPE_IP_ADDRESS (IS_IPv4)) { + const NMPlatformIPXAddress *const my_addr = NMP_OBJECT_CAST_IPX_ADDRESS (my_addr_obj); + + if (my_addr->ax.external) + continue; + + if (IS_IPv4) { + nm_assert (my_addr->a4.plen <= 32); + if (my_addr->a4.plen == 0) + continue; + } + + if (IS_IPv4) { + rx.r4 = (NMPlatformIP4Route) { + .ifindex = self->ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, + .network = my_addr->a4.address, + .plen = 32, + .pref_src = my_addr->a4.address, + .type_coerced = nm_platform_route_type_coerce (RTN_LOCAL), + .scope_inv = nm_platform_route_scope_inv (RT_SCOPE_HOST), + .table_coerced = nm_platform_route_table_coerce (is_vrf ? route_table : RT_TABLE_LOCAL), + }; + } else { + rx.r6 = (NMPlatformIP6Route) { + .ifindex = self->ifindex, + .network = my_addr->a6.address, + .plen = 128, + .type_coerced = nm_platform_route_type_coerce (RTN_LOCAL), + .metric = 0, + .table_coerced = nm_platform_route_table_coerce (is_vrf ? route_table : RT_TABLE_LOCAL), + }; + } + nm_l3_config_data_add_route (self, addr_family, NULL, &rx.rx); + + if (my_addr->ax.plen == 0) + continue; + + if (IS_IPv4) { + network_4 = nm_utils_ip4_address_clear_host_address (my_addr->a4.peer_address, + my_addr->a4.plen); + + if (nm_utils_ip4_address_is_zeronet (network_4)) { + /* Kernel doesn't add device-routes for destinations that + * start with 0.x.y.z. Skip them. */ + continue; + } + + if ( my_addr->a4.plen == 32 + && my_addr->a4.address == my_addr->a4.peer_address) { + /* Kernel doesn't add device-routes for /32 addresses unless + * they have a peer. */ + continue; + } + } else { + if (NM_FLAGS_HAS (my_addr->a6.n_ifa_flags, IFA_F_NOPREFIXROUTE)) + continue; + } + + if (IS_IPv4) { + rx.r4 = (NMPlatformIP4Route) { + .ifindex = self->ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, + .network = network_4, + .plen = my_addr->a4.plen, + .pref_src = my_addr->a4.address, + .table_coerced = nm_platform_route_table_coerce (route_table), + .metric = route_metric, + .scope_inv = nm_platform_route_scope_inv (NM_RT_SCOPE_LINK), + }; + nm_platform_ip_route_normalize (addr_family, &rx.rx); + nm_l3_config_data_add_route (self, addr_family, NULL, &rx.rx); + + if ( IS_IPv4 + && out_ip4_dev_route_blacklist + && ( route_table != RT_TABLE_MAIN + || route_metric != NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE)) { + + rx.r4.table_coerced = nm_platform_route_table_coerce (RT_TABLE_MAIN); + rx.r4.metric = NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE; + nm_platform_ip_route_normalize (addr_family, &rx.rx); + + if (nm_l3_config_data_lookup_route (self, + addr_family, + &rx.rx)) { + /* 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, + nmp_object_new (NMP_OBJECT_TYPE_IP4_ROUTE, &rx)); + } + } + } else { + const gboolean has_peer = !IN6_IS_ADDR_UNSPECIFIED (&my_addr->a6.peer_address); + int routes_i; + + /* If we have an IPv6 peer, we add two /128 routes + * (unless, both addresses are identical). */ + for (routes_i = 0; routes_i < 2; routes_i++) { + struct in6_addr a6_stack; + const struct in6_addr *a6; + guint8 plen; + + if ( routes_i == 1 + && has_peer + && IN6_ARE_ADDR_EQUAL (&my_addr->a6.address, &my_addr->a6.peer_address)) + break; + + if (has_peer) { + if (routes_i == 0) + a6 = &my_addr->a6.address; + else + a6 = &my_addr->a6.peer_address; + plen = 128; + } else { + a6 = nm_utils_ip6_address_clear_host_address (&a6_stack, &my_addr->a6.address, my_addr->a6.plen); + plen = my_addr->a6.plen; + } + + rx.r6 = (NMPlatformIP6Route) { + .ifindex = self->ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, + .table_coerced = nm_platform_route_table_coerce (route_table), + .metric = route_metric, + .network = *a6, + .plen = plen, + }; + nm_platform_ip_route_normalize (addr_family, &rx.rx); + nm_l3_config_data_add_route (self, addr_family, NULL, &rx.rx); + } + } + } + + nm_l3_config_data_iter_obj_for_each (iter, self, my_route_obj, NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4)) { + const NMPlatformIPXRoute *my_route = NMP_OBJECT_CAST_IPX_ROUTE (my_route_obj); + NMPObject *new_route; + NMPlatformIPXRoute *new_r; + const NMIPAddr *p_gateway; + + if ( !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (my_route) + || NM_IS_IP_CONFIG_SOURCE_RTPROT (my_route->rx.rt_source)) + continue; + + p_gateway = nm_platform_ip_route_get_gateway (addr_family, &my_route->rx); + + if (nm_ip_addr_is_null (addr_family, p_gateway)) + continue; + + if (_data_get_direct_route_for_host (self, + addr_family, + p_gateway, + nm_platform_route_table_uncoerce (my_route->rx.table_coerced, TRUE))) + continue; + + new_route = nmp_object_clone (my_route_obj, FALSE); + new_r = NMP_OBJECT_CAST_IPX_ROUTE (new_route); + if (IS_IPv4) { + new_r->r4.network = my_route->r4.gateway; + new_r->r4.plen = 32; + new_r->r4.gateway = 0; + } else { + new_r->r6.network = my_route->r6.gateway; + new_r->r6.plen = 128; + new_r->r6.gateway = in6addr_any; + } + + /* we cannot add the route right away, because that invalidates the iteration. */ + if (!extra_onlink_routes) + extra_onlink_routes = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref); + g_ptr_array_add (extra_onlink_routes, new_route); + } + if (extra_onlink_routes) { + for (i = 0; i < extra_onlink_routes->len; i++) { + nm_l3_config_data_add_route_full (self, + addr_family, + extra_onlink_routes->pdata[i], + NULL, + NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE, + NULL, + NULL); + } + } + + NM_SET_OUT (out_ip4_dev_route_blacklist, g_steal_pointer (&ip4_dev_route_blacklist)); +} + /*****************************************************************************/ static void @@ -841,7 +1503,7 @@ _init_from_connection_ip (NML3ConfigData *self, guint i; int idx; - nm_assert (NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); nm_assert_addr_family (addr_family); nm_assert (!connection || NM_IS_CONNECTION (connection)); @@ -874,7 +1536,7 @@ _init_from_connection_ip (NML3ConfigData *self, }; } - nm_l3_config_data_add_route (self, addr_family, NULL, &r.rx, NULL, NULL); + nm_l3_config_data_add_route (self, addr_family, NULL, &r.rx); } naddresses = nm_setting_ip_config_get_num_addresses (s_ip); @@ -914,7 +1576,7 @@ _init_from_connection_ip (NML3ConfigData *self, nm_assert (a.a6.plen <= 128); } - nm_l3_config_data_add_address (self, addr_family, NULL, &a.ax, NULL); + nm_l3_config_data_add_address (self, addr_family, NULL, &a.ax); } nroutes = nm_setting_ip_config_get_num_routes (s_ip); @@ -968,7 +1630,7 @@ _init_from_connection_ip (NML3ConfigData *self, &r.rx, route_table); - nm_l3_config_data_add_route (self, addr_family, NULL, &r.rx, NULL, NULL); + nm_l3_config_data_add_route (self, addr_family, NULL, &r.rx); } nnameservers = nm_setting_ip_config_get_num_dns (s_ip); @@ -1006,8 +1668,6 @@ NML3ConfigData * nm_l3_config_data_new_from_connection (NMDedupMultiIndex *multi_idx, int ifindex, NMConnection *connection, - NMSettingConnectionMdns mdns, - NMSettingConnectionLlmnr llmnr, guint32 route_table, guint32 route_metric) { @@ -1017,10 +1677,6 @@ nm_l3_config_data_new_from_connection (NMDedupMultiIndex *multi_idx, _init_from_connection_ip (self, AF_INET, connection, route_table, route_metric); _init_from_connection_ip (self, AF_INET6, connection, route_table, route_metric); - - self->mdns = mdns; - self->llmnr = llmnr; - return self; } @@ -1061,7 +1717,7 @@ _init_from_platform (NML3ConfigData *self, const NMPObject *plobj = NULL; NMDedupMultiIter iter; - nm_assert (NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); nm_assert_addr_family (addr_family); head_entry = nm_platform_lookup_object (platform, @@ -1076,8 +1732,7 @@ _init_from_platform (NML3ConfigData *self, self->ifindex, plobj, NULL, - FALSE, - TRUE, + NM_L3_CONFIG_ADD_FLAGS_APPEND_FORCE, NULL, NULL)) nm_assert_not_reached (); @@ -1097,7 +1752,7 @@ _init_from_platform (NML3ConfigData *self, : NMP_OBJECT_TYPE_IP6_ROUTE, self->ifindex); nmp_cache_iter_for_each (&iter, head_entry, &plobj) - nm_l3_config_data_add_route (self, addr_family, plobj, NULL, NULL, NULL); + nm_l3_config_data_add_route (self, addr_family, plobj, NULL); } NML3ConfigData * @@ -1126,15 +1781,148 @@ nm_l3_config_data_new_from_platform (NMDedupMultiIndex *multi_idx, /*****************************************************************************/ +static void +_init_merge (NML3ConfigData *self, + const NML3ConfigData *src, + NML3ConfigMergeFlags merge_flags, + const guint32 *default_route_penalty_x /* length 2, for IS_IPv4 */) +{ + NMDedupMultiIter iter; + const NMPObject *obj; + int IS_IPv4; + + nm_assert (_NM_IS_L3_CONFIG_DATA (self, FALSE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (src, TRUE)); + + for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { + const int addr_family = IS_IPv4 ? AF_INET : AF_INET6; + const NML3ConfigDatFlags has_dns_priority_flag = NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY (IS_IPv4); + + nm_l3_config_data_iter_obj_for_each (iter, + src, + obj, + NMP_OBJECT_TYPE_IP_ADDRESS (IS_IPv4)) { + if ( NM_FLAGS_HAS (merge_flags, NM_L3_CONFIG_MERGE_FLAGS_EXTERNAL) + && !NMP_OBJECT_CAST_IP_ADDRESS (obj)->external) { + NMPlatformIPXAddress a; + + if (IS_IPv4) + a.a4 = *NMP_OBJECT_CAST_IP4_ADDRESS (obj); + else + a.a6 = *NMP_OBJECT_CAST_IP6_ADDRESS (obj); + a.ax.ifindex = self->ifindex; + a.ax.external = TRUE; + nm_l3_config_data_add_address_full (self, + addr_family, + NULL, + &a.ax, + NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE, + NULL); + continue; + } + + nm_l3_config_data_add_address_full (self, + addr_family, + obj, + NULL, + NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE, + NULL); + } + + if (!NM_FLAGS_HAS (merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES)) { + nm_l3_config_data_iter_obj_for_each (iter, + src, + obj, + NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4)) { + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (NMP_OBJECT_CAST_IP_ROUTE (obj))) { + if ( NM_FLAGS_HAS (merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES) + && !NM_FLAGS_HAS (src->flags, NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES)) + continue; + + if ( default_route_penalty_x + && default_route_penalty_x[IS_IPv4] > 0) { + NMPlatformIPXRoute r; + + if (IS_IPv4) + r.r4 = *NMP_OBJECT_CAST_IP4_ROUTE (obj); + else + r.r6 = *NMP_OBJECT_CAST_IP6_ROUTE (obj); + r.rx.ifindex = self->ifindex; + r.rx.metric = nm_utils_ip_route_metric_penalize (r.rx.metric, default_route_penalty_x[IS_IPv4]); + nm_l3_config_data_add_route_full (self, + addr_family, + NULL, + &r.rx, + NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE, + NULL, + NULL); + continue; + } + } + nm_l3_config_data_add_route_full (self, + addr_family, + obj, + NULL, + NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE, + NULL, + NULL); + } + } + + if (!NM_FLAGS_HAS (merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) + _garray_inaddr_merge (&self->nameservers_x[IS_IPv4], src->nameservers_x[IS_IPv4], addr_family); + + if (!NM_FLAGS_HAS (merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) + _strv_ptrarray_merge (&self->domains_x[IS_IPv4], src->domains_x[IS_IPv4]); + + if (!NM_FLAGS_HAS (merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) + _strv_ptrarray_merge (&self->searches_x[IS_IPv4], src->searches_x[IS_IPv4]); + + if (!NM_FLAGS_HAS (merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) + _strv_ptrarray_merge (&self->dns_options_x[IS_IPv4], src->dns_options_x[IS_IPv4]); + + if ( !NM_FLAGS_ANY (self->flags, has_dns_priority_flag) + && NM_FLAGS_ANY (src->flags, has_dns_priority_flag)) { + self->dns_priority_x[IS_IPv4] = src->dns_priority_x[IS_IPv4]; + self->flags |= has_dns_priority_flag; + } + + if (self->route_table_sync_x[IS_IPv4] == NM_IP_ROUTE_TABLE_SYNC_MODE_NONE) + self->route_table_sync_x[IS_IPv4] = src->route_table_sync_x[IS_IPv4]; + } + + if (!NM_FLAGS_HAS (merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) { + _garray_inaddr_merge (&self->wins, src->wins, AF_INET); + _garray_inaddr_merge (&self->nis_servers, src->nis_servers, AF_INET); + + if ( !self->nis_domain + && src->nis_domain) + self->nis_domain = g_strdup (src->nis_domain); + } + + if (self->mdns == NM_SETTING_CONNECTION_MDNS_DEFAULT) + self->mdns = src->mdns; + + if (self->llmnr == NM_SETTING_CONNECTION_LLMNR_DEFAULT) + self->llmnr = src->llmnr; + + if (self->metered == NM_TERNARY_DEFAULT) + self->metered = src->metered; + + if ( !NM_FLAGS_HAS (self->flags, NM_L3_CONFIG_DAT_FLAGS_HAS_MTU) + && NM_FLAGS_HAS (src->flags, NM_L3_CONFIG_DAT_FLAGS_HAS_MTU)) { + self->mtu = src->mtu; + self->flags |= NM_L3_CONFIG_DAT_FLAGS_HAS_MTU; + } +} + NML3ConfigData * nm_l3_config_data_new_clone (const NML3ConfigData *src, int ifindex) { NML3ConfigData *self; - NMDedupMultiIter iter; - const NMPObject *obj; - nm_assert (NM_IS_L3_CONFIG_DATA (src, TRUE)); + nm_assert (_NM_IS_L3_CONFIG_DATA (src, TRUE)); /* pass 0, to use the original ifindex. You can also use this function to * copy the configuration for a different ifindex. */ @@ -1143,32 +1931,30 @@ nm_l3_config_data_new_clone (const NML3ConfigData *src, ifindex = src->ifindex; self = nm_l3_config_data_new (src->multi_idx, ifindex); + _init_merge (self, src, NM_L3_CONFIG_MERGE_FLAGS_NONE, NULL); + return self; +} + +NML3ConfigData * +nm_l3_config_data_new_combined (NMDedupMultiIndex *multi_idx, + int ifindex, + const NML3ConfigDatMergeInfo *const*merge_infos, + guint merge_infos_len) +{ + NML3ConfigData *self; + guint i; + + nm_assert (multi_idx); + nm_assert (ifindex > 0); - nm_l3_config_data_iter_obj_for_each (iter, src, obj, NMP_OBJECT_TYPE_IP4_ADDRESS) - nm_l3_config_data_add_address (self, AF_INET, obj, NULL, NULL); - nm_l3_config_data_iter_obj_for_each (iter, src, obj, NMP_OBJECT_TYPE_IP6_ADDRESS) - nm_l3_config_data_add_address (self, AF_INET6, obj, NULL, NULL); - nm_l3_config_data_iter_obj_for_each (iter, src, obj, NMP_OBJECT_TYPE_IP4_ROUTE) - nm_l3_config_data_add_route (self, AF_INET, obj, NULL, NULL, NULL); - nm_l3_config_data_iter_obj_for_each (iter, src, obj, NMP_OBJECT_TYPE_IP6_ROUTE) - nm_l3_config_data_add_route (self, AF_INET6, obj, NULL, NULL, NULL); - - nm_assert (self->best_default_route_4 == src->best_default_route_4); - nm_assert (self->best_default_route_6 == src->best_default_route_6); - - self->wins_4 = _garray_inaddr_clone (src->wins_4, AF_INET); - self->nameservers_4 = _garray_inaddr_clone (src->nameservers_4, AF_INET); - self->nameservers_6 = _garray_inaddr_clone (src->nameservers_6, AF_INET6); - self->domains_4 = nm_strv_ptrarray_clone (src->domains_4, TRUE); - self->domains_6 = nm_strv_ptrarray_clone (src->domains_6, TRUE); - self->searches_4 = nm_strv_ptrarray_clone (src->searches_4, TRUE); - self->searches_6 = nm_strv_ptrarray_clone (src->searches_6, TRUE); - self->dns_options_4 = nm_strv_ptrarray_clone (src->dns_options_4, TRUE); - self->dns_options_6 = nm_strv_ptrarray_clone (src->dns_options_6, TRUE); - self->dns_priority_4 = src->dns_priority_4; - self->dns_priority_6 = src->dns_priority_6; - self->mdns = src->mdns; - self->llmnr = src->llmnr; + self = nm_l3_config_data_new (multi_idx, ifindex); + + for (i = 0; i < merge_infos_len; i++) { + _init_merge (self, + merge_infos[i]->l3cfg, + merge_infos[i]->merge_flags, + merge_infos[i]->default_route_penalty_x); + } return self; } diff --git a/src/nm-l3-config-data.h b/src/nm-l3-config-data.h index 5b81b57e19..59fbffce66 100644 --- a/src/nm-l3-config-data.h +++ b/src/nm-l3-config-data.h @@ -14,10 +14,69 @@ typedef enum { /* if set, then the merge flag NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES gets * ignored during merge. */ NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES = (1ull << 0), + + NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY_4 = (1ull << 1), + NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY_6 = (1ull << 2), +#define NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY(is_ipv4) ( (is_ipv4) \ + ? NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY_4 \ + : NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY_6) + NM_L3_CONFIG_DAT_FLAGS_HAS_MTU = (1ull << 3), + } NML3ConfigDatFlags; +typedef enum { + NM_L3_CONFIG_ADD_FLAGS_NONE = 0, + + /* If the object does not yet exist, it will be added. If it already exists, + * by default the object will be replaced. With this flag, the new object will + * be merged with the existing one. */ + NM_L3_CONFIG_ADD_FLAGS_MERGE = (1ull << 0), + + /* If the object does not yet exist, it will be added. If it already exists, + * by default the object will be replaced. With this flag, the add will have + * no effect and the existing object will be kept. */ + NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE = (1ull << 1), + + /* A new object gets appended by default. If the object already exists, + * by default it will not be moved. With APPEND-FORCE, we will always move + * an existing object to the end of the list. */ + NM_L3_CONFIG_ADD_FLAGS_APPEND_FORCE = (1ull << 2), +} NML3ConfigAddFlags; + +/** + * NML3ConfigMergeFlags: + * @NM_L3_CONFIG_MERGE_FLAGS_NONE: no flags set + * @NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES: don't merge routes + * @NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES: don't merge default routes. + * Note that if the respective NML3ConfigData has NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES + * set, this flag gets ignored during merge. + * @NM_L3_CONFIG_MERGE_FLAGS_NO_DNS: don't merge DNS information + * @NM_L3_CONFIG_MERGE_FLAGS_EXTERNAL: mark new addresses as external + */ +typedef enum { + NM_L3_CONFIG_MERGE_FLAGS_NONE = 0, + NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES = (1LL << 0), + NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES = (1LL << 1), + NM_L3_CONFIG_MERGE_FLAGS_NO_DNS = (1LL << 2), + NM_L3_CONFIG_MERGE_FLAGS_EXTERNAL = (1LL << 3), +} NML3ConfigMergeFlags; + +/*****************************************************************************/ + typedef struct _NML3ConfigData NML3ConfigData; +typedef struct { + const NML3ConfigData *l3cfg; + NML3ConfigMergeFlags merge_flags; + union { + struct { + guint32 default_route_penalty_6; + guint32 default_route_penalty_4; + }; + guint32 default_route_penalty_x[2]; + }; +} NML3ConfigDatMergeInfo; + NML3ConfigData *nm_l3_config_data_new (NMDedupMultiIndex *multi_idx, int ifindex); const NML3ConfigData *nm_l3_config_data_ref (const NML3ConfigData *self); @@ -39,8 +98,6 @@ NML3ConfigData *nm_l3_config_data_new_clone (const NML3ConfigData *src, NML3ConfigData *nm_l3_config_data_new_from_connection (NMDedupMultiIndex *multi_idx, int ifindex, NMConnection *connection, - NMSettingConnectionMdns mdns, - NMSettingConnectionLlmnr llmnr, guint32 route_table, guint32 route_metric); @@ -49,8 +106,62 @@ NML3ConfigData *nm_l3_config_data_new_from_platform (NMDedupMultiIndex *multi_id NMPlatform *platform, NMSettingIP6ConfigPrivacy ipv6_privacy_rfc4941); +NML3ConfigData *nm_l3_config_data_new_combined (NMDedupMultiIndex *multi_idx, + int ifindex, + const NML3ConfigDatMergeInfo *const*merge_infos, + guint merge_infos_len); + +void nm_l3_config_data_add_dependent_routes (NML3ConfigData *self, + int addr_family, + guint32 route_table, + guint32 route_metric, + gboolean is_vrf, + GPtrArray **out_ip4_dev_route_blacklist); + +/*****************************************************************************/ + +int nm_l3_config_data_get_ifindex (const NML3ConfigData *self); + +NMDedupMultiIndex *nm_l3_config_data_get_multi_idx (const NML3ConfigData *self); + +static inline gboolean +NM_IS_L3_CONFIG_DATA (const NML3ConfigData *self) +{ + /* NML3ConfigData is not an NMObject, so we cannot ask which type it has. + * This check here is really only useful for assertions, and there it is + * enough to check whether the pointer is not NULL. + * + * Additionally, also call nm_l3_config_data_get_ifindex(), which does more + * checks during nm_assert(). */ + nm_assert (nm_l3_config_data_get_ifindex (self) > 0); + return !!self; +} + +/*****************************************************************************/ + +int nm_l3_config_data_cmp (const NML3ConfigData *a, const NML3ConfigData *b); + +static inline gboolean +nm_l3_config_data_equal (const NML3ConfigData *a, const NML3ConfigData *b) +{ + return nm_l3_config_data_cmp (a, b) == 0; +} + /*****************************************************************************/ +const NMDedupMultiIdxType *nm_l3_config_data_lookup_index (const NML3ConfigData *self, + NMPObjectType obj_type); + +const NMDedupMultiEntry *nm_l3_config_data_lookup_obj (const NML3ConfigData *self, + const NMPObject *obj); + +const NMDedupMultiEntry *nm_l3_config_data_lookup_route_obj (const NML3ConfigData *self, + const NMPObject *needle); + +const NMDedupMultiEntry *nm_l3_config_data_lookup_route (const NML3ConfigData *self, + int addr_family, + const NMPlatformIPRoute *needle); + const NMDedupMultiHeadEntry *nm_l3_config_data_lookup_objs (const NML3ConfigData *self, NMPObjectType obj_type); static inline const NMDedupMultiHeadEntry * @@ -102,10 +213,6 @@ nm_l3_config_data_lookup_routes (const NML3ConfigData *self, int addr_family) /*****************************************************************************/ -int nm_l3_config_data_get_ifindex (const NML3ConfigData *self); - -/*****************************************************************************/ - NML3ConfigDatFlags nm_l3_config_data_get_flags (const NML3ConfigData *self); void nm_l3_config_data_set_flags_full (NML3ConfigData *self, @@ -128,43 +235,93 @@ nm_l3_config_data_unset_flags (NML3ConfigData *self, /*****************************************************************************/ -gboolean nm_l3_config_data_add_address (NML3ConfigData *self, - int addr_family, - const NMPObject *obj_new, - const NMPlatformIPAddress *pl_new, - const NMPObject **out_obj_new); +gboolean nm_l3_config_data_add_address_full (NML3ConfigData *self, + int addr_family, + const NMPObject *obj_new, + const NMPlatformIPAddress *pl_new, + NML3ConfigAddFlags add_flags, + const NMPObject **out_obj_new); + +static inline gboolean +nm_l3_config_data_add_address (NML3ConfigData *self, + int addr_family, + const NMPObject *obj_new, + const NMPlatformIPAddress *pl_new) +{ + return nm_l3_config_data_add_address_full (self, + addr_family, + obj_new, + pl_new, + NM_L3_CONFIG_ADD_FLAGS_MERGE, + NULL); +} static inline gboolean nm_l3_config_data_add_address_4 (NML3ConfigData *self, const NMPlatformIP4Address *addr) { - return nm_l3_config_data_add_address (self, AF_INET, NULL, NM_PLATFORM_IP_ADDRESS_CAST (addr), NULL); + return nm_l3_config_data_add_address (self, AF_INET, NULL, NM_PLATFORM_IP_ADDRESS_CAST (addr)); } static inline gboolean nm_l3_config_data_add_address_6 (NML3ConfigData *self, const NMPlatformIP6Address *addr) { - return nm_l3_config_data_add_address (self, AF_INET6, NULL, NM_PLATFORM_IP_ADDRESS_CAST (addr), NULL); + return nm_l3_config_data_add_address (self, AF_INET6, NULL, NM_PLATFORM_IP_ADDRESS_CAST (addr)); } -gboolean nm_l3_config_data_add_route (NML3ConfigData *self, - int addr_family, - const NMPObject *obj_new, - const NMPlatformIPRoute *pl_new, - const NMPObject **out_obj_new, - gboolean *out_changed_best_default_route); +gboolean nm_l3_config_data_add_route_full (NML3ConfigData *self, + int addr_family, + const NMPObject *obj_new, + const NMPlatformIPRoute *pl_new, + NML3ConfigAddFlags add_flags, + const NMPObject **out_obj_new, + gboolean *out_changed_best_default_route); + +static inline gboolean +nm_l3_config_data_add_route (NML3ConfigData *self, + int addr_family, + const NMPObject *obj_new, + const NMPlatformIPRoute *pl_new) +{ + return nm_l3_config_data_add_route_full (self, + addr_family, + obj_new, + pl_new, + NM_L3_CONFIG_ADD_FLAGS_MERGE, + NULL, + NULL); +} static inline gboolean nm_l3_config_data_add_route_4 (NML3ConfigData *self, const NMPlatformIP4Route *rt) { - return nm_l3_config_data_add_route (self, AF_INET, NULL, NM_PLATFORM_IP_ROUTE_CAST (rt), NULL, NULL); + return nm_l3_config_data_add_route (self, AF_INET, NULL, NM_PLATFORM_IP_ROUTE_CAST (rt)); } static inline gboolean nm_l3_config_data_add_route_6 (NML3ConfigData *self, const NMPlatformIP6Route *rt) { - return nm_l3_config_data_add_route (self, AF_INET6, NULL, NM_PLATFORM_IP_ROUTE_CAST (rt), NULL, NULL); + return nm_l3_config_data_add_route (self, AF_INET6, NULL, NM_PLATFORM_IP_ROUTE_CAST (rt)); } +gboolean nm_l3_config_data_set_mdns (NML3ConfigData *self, + NMSettingConnectionMdns mdns); + +gboolean nm_l3_config_data_set_llmnr (NML3ConfigData *self, + NMSettingConnectionLlmnr llmnr); + +NMIPRouteTableSyncMode nm_l3_config_data_get_route_table_sync (const NML3ConfigData *self, + int addr_family); + +gboolean nm_l3_config_data_set_route_table_sync (NML3ConfigData *self, + int addr_family, + NMIPRouteTableSyncMode route_table_sync); + +gboolean nm_l3_config_data_set_metered (NML3ConfigData *self, + NMTernary metered); + +gboolean nm_l3_config_data_set_mtu (NML3ConfigData *self, + guint32 mtu); + gboolean nm_l3_config_data_add_nameserver (NML3ConfigData *self, int addr_family, gconstpointer /* (const NMIPAddr *) */ nameserver); @@ -172,6 +329,12 @@ gboolean nm_l3_config_data_add_nameserver (NML3ConfigData *self, gboolean nm_l3_config_data_add_wins (NML3ConfigData *self, in_addr_t wins); +gboolean nm_l3_config_data_add_nis_server (NML3ConfigData *self, + in_addr_t nis_server); + +gboolean nm_l3_config_data_set_nis_domain (NML3ConfigData *self, + const char *nis_domain); + gboolean nm_l3_config_data_add_domain (NML3ConfigData *self, int addr_family, const char *domain); diff --git a/src/nm-l3cfg.c b/src/nm-l3cfg.c index aefaef1cd8..5e0890ded2 100644 --- a/src/nm-l3cfg.c +++ b/src/nm-l3cfg.c @@ -10,13 +10,58 @@ /*****************************************************************************/ +typedef struct { + NML3ConfigDatMergeInfo merge_info; + gconstpointer tag; + guint64 pseudo_timestamp; + int priority; + bool dirty:1; +} L3ConfigData; + +/*****************************************************************************/ + NM_GOBJECT_PROPERTIES_DEFINE (NML3Cfg, PROP_NETNS, PROP_IFINDEX, ); +enum { + SIGNAL_NOTIFY, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static GQuark signal_notify_quarks[_NM_L3_CONFIG_NOTIFY_TYPE_NUM]; + typedef struct _NML3CfgPrivate { GArray *property_emit_list; + GArray *l3_config_datas; + const NML3ConfigData *combined_l3cfg; + + GHashTable *routes_temporary_not_available_hash; + + GHashTable *externally_removed_objs_hash; + + guint64 pseudo_timestamp_counter; + + union { + struct { + guint externally_removed_objs_cnt_addresses_6; + guint externally_removed_objs_cnt_addresses_4; + }; + guint externally_removed_objs_cnt_addresses_x[2]; + }; + + union { + struct { + guint externally_removed_objs_cnt_routes_6; + guint externally_removed_objs_cnt_routes_4; + }; + guint externally_removed_objs_cnt_routes_x[2]; + }; + + guint routes_temporary_not_available_id; } NML3CfgPrivate; struct _NML3CfgClass { @@ -44,6 +89,214 @@ static void _property_emit_notify (NML3Cfg *self, NML3CfgPropertyEmitType emit_t /*****************************************************************************/ +static +NM_UTILS_ENUM2STR_DEFINE (_l3_cfg_commit_type_to_string, NML3CfgCommitType, + NM_UTILS_ENUM2STR (NM_L3_CFG_COMMIT_TYPE_ASSUME, "assume"), + NM_UTILS_ENUM2STR (NM_L3_CFG_COMMIT_TYPE_UPDATE, "update"), + NM_UTILS_ENUM2STR (NM_L3_CFG_COMMIT_TYPE_REAPPLY, "reapply"), +); + +/*****************************************************************************/ + +static void +_l3cfg_emit_signal_notify (NML3Cfg *self, + NML3ConfigNotifyType notify_type, + gpointer pay_load) +{ + nm_assert (_NM_INT_NOT_NEGATIVE (notify_type)); + nm_assert (notify_type < G_N_ELEMENTS (signal_notify_quarks)); + + g_signal_emit (self, + signals[SIGNAL_NOTIFY], + signal_notify_quarks[notify_type], + (int) notify_type, + pay_load); +} + +/*****************************************************************************/ + +static guint * +_l3cfg_externally_removed_objs_counter (NML3Cfg *self, + NMPObjectType obj_type) +{ + switch (obj_type) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + return &self->priv.p->externally_removed_objs_cnt_addresses_4; + case NMP_OBJECT_TYPE_IP6_ADDRESS: + return &self->priv.p->externally_removed_objs_cnt_addresses_6; + case NMP_OBJECT_TYPE_IP4_ROUTE: + return &self->priv.p->externally_removed_objs_cnt_routes_4; + case NMP_OBJECT_TYPE_IP6_ROUTE: + return &self->priv.p->externally_removed_objs_cnt_routes_6; + default: + return nm_assert_unreachable_val (NULL); + } +} + +static void +_l3cfg_externally_removed_objs_drop (NML3Cfg *self, + int addr_family) +{ + const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family); + GHashTableIter iter; + const NMPObject *obj; + + nm_assert (NM_IS_L3CFG (self)); + nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + if (addr_family == AF_UNSPEC) { + self->priv.p->externally_removed_objs_cnt_addresses_4 = 0; + self->priv.p->externally_removed_objs_cnt_addresses_6 = 0; + self->priv.p->externally_removed_objs_cnt_routes_4 = 0; + self->priv.p->externally_removed_objs_cnt_routes_6 = 0; + if (g_hash_table_size (self->priv.p->externally_removed_objs_hash) > 0) + _LOGD ("externally-removed: untrack all"); + nm_clear_pointer (&self->priv.p->externally_removed_objs_hash, g_hash_table_unref); + return; + } + + if ( self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] == 0 + && self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] == 0) + return; + + _LOGD ("externally-removed: untrack IPv%c", + nm_utils_addr_family_to_char (addr_family)); + + g_hash_table_iter_init (&iter, self->priv.p->externally_removed_objs_hash); + while (g_hash_table_iter_next (&iter, (gpointer *) &obj, NULL)) { + nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE)); + if (NMP_OBJECT_GET_ADDR_FAMILY (obj) != addr_family) + g_hash_table_iter_remove (&iter); + } + self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] = 0; + self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] = 0; + + if ( self->priv.p->externally_removed_objs_cnt_addresses_x[!IS_IPv4] == 0 + && self->priv.p->externally_removed_objs_cnt_routes_x[!IS_IPv4] == 0) + nm_clear_pointer (&self->priv.p->externally_removed_objs_hash, g_hash_table_unref); +} + +static void +_l3cfg_externally_removed_objs_drop_unused (NML3Cfg *self) +{ + GHashTableIter h_iter; + const NMPObject *obj; + char sbuf[sizeof (_nm_utils_to_string_buffer)]; + + nm_assert (NM_IS_L3CFG (self)); + + if (!self->priv.p->externally_removed_objs_hash) + return; + + if (!self->priv.p->combined_l3cfg) { + _l3cfg_externally_removed_objs_drop (self, AF_UNSPEC); + return; + } + + g_hash_table_iter_init (&h_iter, self->priv.p->externally_removed_objs_hash); + while (g_hash_table_iter_next (&h_iter, (gpointer *) &obj, NULL)) { + if (!nm_l3_config_data_lookup_route_obj (self->priv.p->combined_l3cfg, + obj)) { + /* The object is no longer tracked in the configuration. + * The externally_removed_objs_hash is to prevent adding entires that were + * removed externally, so if we don't plan to add the entry, we no longer need to track + * it. */ + (*(_l3cfg_externally_removed_objs_counter (self, NMP_OBJECT_GET_TYPE (obj))))--; + g_hash_table_iter_remove (&h_iter); + _LOGD ("externally-removed: untrack %s", + nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf))); + } + } +} + +static void +_l3cfg_externally_removed_objs_track (NML3Cfg *self, + const NMPObject *obj, + gboolean is_removed) +{ + char sbuf[1000]; + + nm_assert (NM_IS_L3CFG (self)); + + if (!self->priv.p->combined_l3cfg) + return; + + if (!is_removed) { + /* the object is still (or again) present. It no longer gets hidden. */ + if (self->priv.p->externally_removed_objs_hash) { + if (g_hash_table_remove (self->priv.p->externally_removed_objs_hash, + obj)) { + (*(_l3cfg_externally_removed_objs_counter (self, + NMP_OBJECT_GET_TYPE (obj))))--; + _LOGD ("externally-removed: untrack %s", + nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf))); + } + } + return; + } + + if (!nm_l3_config_data_lookup_route_obj (self->priv.p->combined_l3cfg, + obj)) { + /* we don't care about this object, so there is nothing to hide hide */ + return; + } + + if (G_UNLIKELY (!self->priv.p->externally_removed_objs_hash)) { + self->priv.p->externally_removed_objs_hash = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal, + (GDestroyNotify) nmp_object_unref, + NULL); + } + + if (g_hash_table_add (self->priv.p->externally_removed_objs_hash, + (gpointer) nmp_object_ref (obj))) { + (*(_l3cfg_externally_removed_objs_counter (self, + NMP_OBJECT_GET_TYPE (obj))))++; + _LOGD ("externally-removed: track %s", + nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf))); + } +} + +static void +_l3cfg_externally_removed_objs_pickup (NML3Cfg *self, + int addr_family) +{ + const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family); + NMDedupMultiIter iter; + const NMPObject *obj; + + if (!self->priv.p->combined_l3cfg) + return; + + nm_l3_config_data_iter_obj_for_each (iter, self->priv.p->combined_l3cfg, obj, NMP_OBJECT_TYPE_IP_ADDRESS (IS_IPv4)) { + if (!nm_platform_lookup_entry (self->priv.platform, + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + obj)) + _l3cfg_externally_removed_objs_track (self, obj, TRUE); + } + nm_l3_config_data_iter_obj_for_each (iter, self->priv.p->combined_l3cfg, obj, NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4)) { + if (!nm_platform_lookup_entry (self->priv.platform, + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + obj)) + _l3cfg_externally_removed_objs_track (self, obj, TRUE); + } +} + +static gboolean +_l3cfg_externally_removed_objs_filter (/* const NMDedupMultiObj * */ gconstpointer o, + gpointer user_data) +{ + const NMPObject *obj = o; + GHashTable *externally_removed_objs_hash = user_data; + + return !g_hash_table_contains (externally_removed_objs_hash, obj); +} + +/*****************************************************************************/ + static void _load_link (NML3Cfg *self, gboolean initial) { @@ -87,6 +340,26 @@ _nm_l3cfg_notify_platform_change_on_idle (NML3Cfg *self, guint32 obj_type_flags) _property_emit_notify (self, NM_L3CFG_PROPERTY_EMIT_TYPE_IP6_ROUTE); } +void +_nm_l3cfg_notify_platform_change (NML3Cfg *self, + NMPlatformSignalChangeType change_type, + const NMPObject *obj) +{ + nm_assert (NMP_OBJECT_IS_VALID (obj)); + + switch (NMP_OBJECT_GET_TYPE (obj)) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + _l3cfg_externally_removed_objs_track (self, + obj, + change_type == NM_PLATFORM_SIGNAL_REMOVED); + default: + break; + } +} + /*****************************************************************************/ typedef struct { @@ -226,6 +499,654 @@ nm_l3cfg_property_emit_unregister (NML3Cfg *self, /*****************************************************************************/ +static GArray * +_l3_config_datas_ensure (GArray **p_arr) +{ + if (!*p_arr) + *p_arr = g_array_new (FALSE, FALSE, sizeof (L3ConfigData)); + return *p_arr; +} + +#define _l3_config_datas_at(l3_config_datas, idx) \ + (&g_array_index ((l3_config_datas), L3ConfigData, (idx))) + +static gssize +_l3_config_datas_find_next (GArray *l3_config_datas, + guint start_idx, + gconstpointer needle_tag, + const NML3ConfigData *needle_l3cfg) +{ + guint i; + + nm_assert (l3_config_datas); + nm_assert (start_idx <= l3_config_datas->len); + + for (i = start_idx; i < l3_config_datas->len; i++) { + const L3ConfigData *l3_config_data = _l3_config_datas_at (l3_config_datas, i); + + if ( NM_IN_SET (needle_tag, NULL, l3_config_data->tag) + && NM_IN_SET (needle_l3cfg, NULL, l3_config_data->merge_info.l3cfg)) + return i; + } + return -1; +} + +static void +_l3_config_datas_remove_index_fast (GArray *arr, + guint idx) +{ + L3ConfigData *l3_config_data; + + nm_assert (arr); + nm_assert (idx < arr->len); + + l3_config_data = _l3_config_datas_at (arr, idx); + + nm_l3_config_data_unref (l3_config_data->merge_info.l3cfg); + + g_array_remove_index_fast (arr, idx); +} + +void +nm_l3cfg_mark_config_dirty (NML3Cfg *self, + gconstpointer tag, + gboolean dirty) +{ + GArray *l3_config_datas; + gssize idx; + + nm_assert (NM_IS_L3CFG (self)); + nm_assert (tag); + + l3_config_datas = self->priv.p->l3_config_datas; + if (!l3_config_datas) + return; + + idx = 0; + while (TRUE) { + idx = _l3_config_datas_find_next (l3_config_datas, + idx, + tag, + NULL); + if (idx < 0) + return; + + _l3_config_datas_at (l3_config_datas, idx)->dirty = dirty; + idx++; + } +} + +void +nm_l3cfg_add_config (NML3Cfg *self, + gconstpointer tag, + gboolean replace_same_tag, + const NML3ConfigData *l3cfg, + int priority, + guint32 default_route_penalty_4, + guint32 default_route_penalty_6, + NML3ConfigMergeFlags merge_flags) +{ + GArray *l3_config_datas; + L3ConfigData *l3_config_data; + gssize idx; + gboolean changed = FALSE; + + nm_assert (NM_IS_L3CFG (self)); + nm_assert (tag); + nm_assert (l3cfg); + nm_assert (nm_l3_config_data_get_ifindex (l3cfg) == self->priv.ifindex); + + l3_config_datas = _l3_config_datas_ensure (&self->priv.p->l3_config_datas); + + idx = _l3_config_datas_find_next (l3_config_datas, + 0, + tag, + replace_same_tag ? NULL : l3cfg); + + if (replace_same_tag) { + gssize idx2; + + idx2 = idx; + idx = -1; + while (TRUE) { + l3_config_data = _l3_config_datas_at (l3_config_datas, idx2); + + if (l3_config_data->merge_info.l3cfg == l3cfg) { + nm_assert (idx == -1); + idx = idx2; + continue; + } + + changed = TRUE; + _l3_config_datas_remove_index_fast (l3_config_datas, idx2); + + idx2 = _l3_config_datas_find_next (l3_config_datas, idx2, tag, NULL); + if (idx2 < 0) + break; + } + } + + if (idx < 0) { + l3_config_data = nm_g_array_append_new (l3_config_datas, L3ConfigData); + *l3_config_data = (L3ConfigData) { + .tag = tag, + .merge_info.l3cfg = nm_l3_config_data_ref_and_seal (l3cfg), + .merge_info.merge_flags = merge_flags, + .merge_info.default_route_penalty_4 = default_route_penalty_4, + .merge_info.default_route_penalty_6 = default_route_penalty_6, + .priority = priority, + .pseudo_timestamp = ++self->priv.p->pseudo_timestamp_counter, + .dirty = FALSE, + }; + changed = TRUE; + } else { + l3_config_data = _l3_config_datas_at (l3_config_datas, idx); + l3_config_data->dirty = FALSE; + nm_assert (l3_config_data->tag == tag); + nm_assert (l3_config_data->merge_info.l3cfg == l3cfg); + if (l3_config_data->priority != priority) { + l3_config_data->priority = priority; + changed = TRUE; + } + if (l3_config_data->merge_info.merge_flags != merge_flags) { + l3_config_data->merge_info.merge_flags = merge_flags; + changed = TRUE; + } + if (l3_config_data->merge_info.default_route_penalty_4 != default_route_penalty_4) { + l3_config_data->merge_info.default_route_penalty_4 = default_route_penalty_4; + changed = TRUE; + } + if (l3_config_data->merge_info.default_route_penalty_6 != default_route_penalty_6) { + l3_config_data->merge_info.default_route_penalty_6 = default_route_penalty_6; + changed = TRUE; + } + } + + if (changed) + self->priv.changed_configs = TRUE; +} + +static void +_l3cfg_remove_config (NML3Cfg *self, + gconstpointer tag, + gboolean only_dirty, + const NML3ConfigData *l3cfg) +{ + GArray *l3_config_datas; + gssize idx; + + nm_assert (NM_IS_L3CFG (self)); + nm_assert (tag); + + l3_config_datas = self->priv.p->l3_config_datas; + if (!l3_config_datas) + return; + + idx = 0; + while (TRUE) { + idx = _l3_config_datas_find_next (l3_config_datas, + idx, + tag, + l3cfg); + if (idx < 0) + return; + + if ( only_dirty + && !_l3_config_datas_at (l3_config_datas, idx)->dirty) { + idx++; + continue; + } + + self->priv.changed_configs = TRUE; + _l3_config_datas_remove_index_fast (l3_config_datas, idx); + if (!l3cfg) + return; + } +} + +void +nm_l3cfg_remove_config (NML3Cfg *self, + gconstpointer tag, + const NML3ConfigData *ifcfg) +{ + nm_assert (ifcfg); + + _l3cfg_remove_config (self, tag, FALSE, ifcfg); +} + +void +nm_l3cfg_remove_config_all (NML3Cfg *self, + gconstpointer tag, + gboolean only_dirty) +{ + _l3cfg_remove_config (self, tag, only_dirty, NULL); +} + +/*****************************************************************************/ + +static int +_l3_config_combine_sort_fcn (gconstpointer p_a, + gconstpointer p_b, + gpointer user_data) +{ + const L3ConfigData *a = *((L3ConfigData **) p_a); + const L3ConfigData *b = *((L3ConfigData **) p_b); + + nm_assert (a); + nm_assert (b); + nm_assert (nm_l3_config_data_get_ifindex (a->merge_info.l3cfg) == nm_l3_config_data_get_ifindex (b->merge_info.l3cfg)); + + /* we sort the entries with higher priority (more important, lower numerical value) + * first. */ + NM_CMP_FIELD (a, b, priority); + + /* if the priority is not unique, we sort them in the order they were added, + * with the oldest first (lower numerical value). */ + NM_CMP_FIELD (a, b, pseudo_timestamp); + + return nm_assert_unreachable_val (0); +} + +static const NML3ConfigData * +_l3cfg_combine_config (GArray *l3_config_datas, + NMDedupMultiIndex *multi_idx, + int ifindex) +{ + gs_free L3ConfigData **infos_heap = NULL; + NML3ConfigData *l3cfg; + L3ConfigData **infos; + guint i; + + if ( !l3_config_datas + || l3_config_datas->len == 0) + return NULL; + + if (l3_config_datas->len == 1) + return nm_l3_config_data_ref (_l3_config_datas_at (l3_config_datas, 0)->merge_info.l3cfg); + + if (l3_config_datas->len < 300 / sizeof (infos[0])) + infos = g_alloca (l3_config_datas->len * sizeof (infos[0])); + else { + infos_heap = g_new (L3ConfigData *, l3_config_datas->len); + infos = infos_heap; + } + + for (i = 0; i < l3_config_datas->len; i++) + infos[i] = _l3_config_datas_at (l3_config_datas, i); + + g_qsort_with_data (infos, + l3_config_datas->len, + sizeof (infos[0]), + _l3_config_combine_sort_fcn, + NULL); + + nm_assert (&infos[0]->merge_info == (NML3ConfigDatMergeInfo *) infos[0]); + + l3cfg = nm_l3_config_data_new_combined (multi_idx, + ifindex, + (const NML3ConfigDatMergeInfo *const*) infos, + l3_config_datas->len); + + nm_assert (l3cfg); + nm_assert (nm_l3_config_data_get_ifindex (l3cfg) == ifindex); + + return nm_l3_config_data_seal (l3cfg); +} + +static gboolean +_l3cfg_update_combined_config (NML3Cfg *self, + const NML3ConfigData **out_old /* transfer reference */) +{ + nm_auto_unref_l3cfg const NML3ConfigData *l3cfg_old = NULL; + nm_auto_unref_l3cfg const NML3ConfigData *l3cfg = NULL; + + nm_assert (NM_IS_L3CFG (self)); + nm_assert (!out_old || !*out_old); + + if (!self->priv.changed_configs) + return FALSE; + + self->priv.changed_configs = FALSE; + + l3cfg = _l3cfg_combine_config (self->priv.p->l3_config_datas, + nm_platform_get_multi_idx (self->priv.platform), + self->priv.ifindex); + + if (nm_l3_config_data_equal (l3cfg, self->priv.p->combined_l3cfg)) + return FALSE; + + _LOGT ("desired IP configuration changed"); + + l3cfg_old = g_steal_pointer (&self->priv.p->combined_l3cfg); + self->priv.p->combined_l3cfg = nm_l3_config_data_seal (g_steal_pointer (&l3cfg)); + NM_SET_OUT (out_old, nm_l3_config_data_ref (self->priv.p->combined_l3cfg)); + return TRUE; +} + +/*****************************************************************************/ + +typedef struct { + const NMPObject *obj; + gint64 timestamp_msec; + bool dirty; +} RoutesTemporaryNotAvailableData; + +static void +_routes_temporary_not_available_data_free (gpointer user_data) +{ + RoutesTemporaryNotAvailableData *data = user_data; + + nmp_object_unref (data->obj); + nm_g_slice_free (data); +} + +#define ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC ((gint64) 20000) + +static gboolean +_routes_temporary_not_available_timeout (gpointer user_data) +{ + RoutesTemporaryNotAvailableData *data; + NML3Cfg *self = NM_L3CFG (user_data); + GHashTableIter iter; + gint64 expiry_threshold_msec; + gboolean any_expired = FALSE; + gint64 now_msec; + gint64 oldest_msec; + + self->priv.p->routes_temporary_not_available_id = 0; + + if (!self->priv.p->routes_temporary_not_available_hash) + return G_SOURCE_REMOVE; + + /* we check the timeouts again. That is, because we allow to remove + * entries from routes_temporary_not_available_hash, without rescheduling + * out timeouts. */ + + now_msec = nm_utils_get_monotonic_timestamp_msec (); + + expiry_threshold_msec = now_msec - ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC; + oldest_msec = G_MAXINT64; + + g_hash_table_iter_init (&iter, self->priv.p->routes_temporary_not_available_hash); + while (g_hash_table_iter_next (&iter, (gpointer *) &data, NULL)) { + if (data->timestamp_msec >= expiry_threshold_msec) { + any_expired = TRUE; + break; + } + if (data->timestamp_msec < oldest_msec) + oldest_msec = data->timestamp_msec; + } + + if (any_expired) { + /* a route expired. We emit a signal, but we don't schedule it again. That will + * only happen if the user calls nm_l3cfg_platform_commit() again. */ + _l3cfg_emit_signal_notify (self, NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED, NULL); + return G_SOURCE_REMOVE; + } + + if (oldest_msec != G_MAXINT64) { + /* we have a timeout still. Reschedule. */ + self->priv.p->routes_temporary_not_available_id = g_timeout_add (oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec, + _routes_temporary_not_available_timeout, + self); + } + return G_SOURCE_REMOVE; +} + +static gboolean +_routes_temporary_not_available_update (NML3Cfg *self, + int addr_family, + GPtrArray *routes_temporary_not_available_arr) +{ + + RoutesTemporaryNotAvailableData *data; + GHashTableIter iter; + gint64 oldest_msec; + gint64 now_msec; + gboolean prune_all = FALSE; + gboolean success = TRUE; + guint i; + + now_msec = nm_utils_get_monotonic_timestamp_msec (); + + if (nm_g_ptr_array_len (routes_temporary_not_available_arr) <= 0) { + prune_all = TRUE; + goto out_prune; + } + + if (self->priv.p->routes_temporary_not_available_hash) { + g_hash_table_iter_init (&iter, self->priv.p->routes_temporary_not_available_hash); + while (g_hash_table_iter_next (&iter, (gpointer *) &data, NULL)) { + if (NMP_OBJECT_GET_ADDR_FAMILY (data->obj) == addr_family) + data->dirty = TRUE; + } + } else { + self->priv.p->routes_temporary_not_available_hash = g_hash_table_new_full (nmp_object_indirect_id_hash, + nmp_object_indirect_id_equal, + _routes_temporary_not_available_data_free, + NULL); + } + + for (i = 0; i < routes_temporary_not_available_arr->len; i++) { + const NMPObject *o = routes_temporary_not_available_arr->pdata[i]; + char sbuf[1024]; + + nm_assert (NMP_OBJECT_GET_TYPE (o) == NMP_OBJECT_TYPE_IP_ROUTE (NM_IS_IPv4 (addr_family))); + + data = g_hash_table_lookup (self->priv.p->routes_temporary_not_available_hash, &o); + + if (data) { + if (!data->dirty) + continue; + + nm_assert ( data->timestamp_msec > 0 + && data->timestamp_msec <= now_msec); + + if (now_msec > data->timestamp_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) { + + /* timeout. Could not add this address. */ + _LOGW ("failure to add IPv%c route: %s", + nm_utils_addr_family_to_char (addr_family), + nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf))); + success = FALSE; + continue; + } + + data->dirty = FALSE; + continue; + } + + _LOGT ("(temporarily) unable to add IPv%c route: %s", + nm_utils_addr_family_to_char (addr_family), + nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf))); + + data = g_slice_new (RoutesTemporaryNotAvailableData); + *data = (RoutesTemporaryNotAvailableData) { + .obj = nmp_object_ref (o), + .timestamp_msec = now_msec, + .dirty = FALSE, + }; + g_hash_table_add (self->priv.p->routes_temporary_not_available_hash, data); + } + +out_prune: + oldest_msec = G_MAXINT64; + + if (self->priv.p->routes_temporary_not_available_hash) { + g_hash_table_iter_init (&iter, self->priv.p->routes_temporary_not_available_hash); + while (g_hash_table_iter_next (&iter, (gpointer *) &data, NULL)) { + nm_assert ( NMP_OBJECT_GET_ADDR_FAMILY (data->obj) == addr_family + || !data->dirty); + if ( !prune_all + && !data->dirty) { + if (data->timestamp_msec < oldest_msec) + oldest_msec = data->timestamp_msec; + continue; + } + g_hash_table_iter_remove (&iter); + } + if (oldest_msec != G_MAXINT64) + nm_clear_pointer (&self->priv.p->routes_temporary_not_available_hash, g_hash_table_unref); + } + + nm_clear_g_source (&self->priv.p->routes_temporary_not_available_id); + if (oldest_msec != G_MAXINT64) { + nm_assert (oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC < now_msec); + self->priv.p->routes_temporary_not_available_id = g_timeout_add (oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec, + _routes_temporary_not_available_timeout, + self); + } + + return success; +} + +/*****************************************************************************/ + +gboolean +nm_l3cfg_platform_commit (NML3Cfg *self, + NML3CfgCommitType commit_type, + int addr_family, + gboolean *out_final_failure_for_temporary_not_available) +{ + nm_auto_unref_l3cfg const NML3ConfigData *l3cfg_old = NULL; + gs_unref_ptrarray GPtrArray *addresses = NULL; + gs_unref_ptrarray GPtrArray *routes = NULL; + gs_unref_ptrarray GPtrArray *addresses_prune = NULL; + gs_unref_ptrarray GPtrArray *routes_prune = NULL; + gs_unref_ptrarray GPtrArray *routes_temporary_not_available_arr = NULL; + NMIPRouteTableSyncMode route_table_sync = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE; + gboolean final_failure_for_temporary_not_available = FALSE; + char sbuf_commit_type[50]; + gboolean combined_changed; + gboolean success = TRUE; + int IS_IPv4; + + g_return_val_if_fail (NM_IS_L3CFG (self), FALSE); + nm_assert (NM_IN_SET (commit_type, NM_L3_CFG_COMMIT_TYPE_REAPPLY, + NM_L3_CFG_COMMIT_TYPE_UPDATE, + NM_L3_CFG_COMMIT_TYPE_ASSUME)); + + if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY) + _l3cfg_externally_removed_objs_drop (self, addr_family); + + if (addr_family == AF_UNSPEC) { + gboolean final_failure_for_temporary_not_available_6 = FALSE; + + if (!nm_l3cfg_platform_commit (self, AF_INET, commit_type, &final_failure_for_temporary_not_available)) + success = FALSE; + if (!nm_l3cfg_platform_commit (self, AF_INET6, commit_type, &final_failure_for_temporary_not_available_6)) + success = FALSE; + NM_SET_OUT (out_final_failure_for_temporary_not_available, + ( final_failure_for_temporary_not_available + || final_failure_for_temporary_not_available_6)); + return success; + } + + _LOGT ("committing IPv%c configuration (%s)", + nm_utils_addr_family_to_char (addr_family), + _l3_cfg_commit_type_to_string (commit_type, sbuf_commit_type, sizeof (sbuf_commit_type))); + + combined_changed = _l3cfg_update_combined_config (self, &l3cfg_old); + + IS_IPv4 = NM_IS_IPv4 (addr_family); + + if (combined_changed) { + /* our combined configuration changed. We may track entries in externally_removed_objs_hash, + * which are not longer to be considered by our configuration. We need to forget about them. */ + _l3cfg_externally_removed_objs_drop_unused (self); + } + + if (commit_type == NM_L3_CFG_COMMIT_TYPE_ASSUME) { + /* we need to artificially pre-populate the externally remove hash. */ + _l3cfg_externally_removed_objs_pickup (self, addr_family); + } + + if (self->priv.p->combined_l3cfg) { + NMDedupMultiFcnSelectPredicate predicate; + + if ( commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY + && self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] > 0) + predicate = _l3cfg_externally_removed_objs_filter; + else + predicate = NULL; + addresses = nm_dedup_multi_objs_to_ptr_array_head (nm_l3_config_data_lookup_objs (self->priv.p->combined_l3cfg, + NMP_OBJECT_TYPE_IP_ADDRESS (IS_IPv4)), + predicate, + self->priv.p->externally_removed_objs_hash); + + if ( commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY + && self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] > 0) + predicate = _l3cfg_externally_removed_objs_filter; + else + predicate = NULL; + routes = nm_dedup_multi_objs_to_ptr_array_head (nm_l3_config_data_lookup_objs (self->priv.p->combined_l3cfg, + NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4)), + predicate, + self->priv.p->externally_removed_objs_hash); + + route_table_sync = nm_l3_config_data_get_route_table_sync (self->priv.p->combined_l3cfg, addr_family); + } + + if (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_NONE) + route_table_sync = NM_IP_ROUTE_TABLE_SYNC_MODE_ALL; + + if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY) { + addresses_prune = nm_platform_ip_address_get_prune_list (self->priv.platform, + addr_family, + self->priv.ifindex, + TRUE); + routes_prune = nm_platform_ip_route_get_prune_list (self->priv.platform, + addr_family, + self->priv.ifindex, + route_table_sync); + } else if (commit_type == NM_L3_CFG_COMMIT_TYPE_UPDATE) { + /* during update, we do a cross with the previous configuration. + * + * Of course, if an entry is both to be pruned and to be added, then + * the latter wins. So, this works just nicely. */ + if (l3cfg_old) { + const NMDedupMultiHeadEntry *head_entry; + + head_entry = nm_l3_config_data_lookup_objs (l3cfg_old, + NMP_OBJECT_TYPE_IP_ADDRESS (IS_IPv4)); + addresses_prune = nm_dedup_multi_objs_to_ptr_array_head (head_entry, + NULL, + NULL); + + head_entry = nm_l3_config_data_lookup_objs (l3cfg_old, + NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4)); + addresses_prune = nm_dedup_multi_objs_to_ptr_array_head (head_entry, + NULL, + NULL); + } + } + + nm_platform_ip_address_sync (self->priv.platform, + addr_family, + self->priv.ifindex, + addresses, + addresses_prune); + + if (!nm_platform_ip_route_sync (self->priv.platform, + addr_family, + self->priv.ifindex, + routes, + routes_prune, + &routes_temporary_not_available_arr)) + success = FALSE; + + final_failure_for_temporary_not_available = FALSE; + if (!_routes_temporary_not_available_update (self, + addr_family, + routes_temporary_not_available_arr)) + final_failure_for_temporary_not_available = TRUE; + + NM_SET_OUT (out_final_failure_for_temporary_not_available, final_failure_for_temporary_not_available); + return success; +} + +/*****************************************************************************/ + static void set_property (GObject *object, guint prop_id, @@ -301,9 +1222,16 @@ finalize (GObject *object) nm_assert (nm_g_array_len (self->priv.p->property_emit_list) == 0u); + nm_clear_g_source (&self->priv.p->routes_temporary_not_available_id); + nm_clear_pointer (&self->priv.p->routes_temporary_not_available_hash, g_hash_table_unref); + + nm_clear_pointer (&self->priv.p->externally_removed_objs_hash, g_hash_table_unref); + g_clear_object (&self->priv.netns); g_clear_object (&self->priv.platform); + nm_clear_pointer (&self->priv.p->combined_l3cfg, nm_l3_config_data_unref); + nm_clear_pointer (&self->priv.pllink, nmp_object_unref); _LOGT ("finalized"); @@ -338,4 +1266,17 @@ nm_l3cfg_class_init (NML3CfgClass *klass) G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + signals[SIGNAL_NOTIFY] = + g_signal_new (NM_L3CFG_SIGNAL_NOTIFY, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_DETAILED + | G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 2, + G_TYPE_INT /* NML3ConfigNotifyType */, + G_TYPE_POINTER /* pay-load */ ); + + signal_notify_quarks[NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED] = g_quark_from_static_string (NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED_DETAIL); } diff --git a/src/nm-l3cfg.h b/src/nm-l3cfg.h index 7fc4b4b7d2..55a062156d 100644 --- a/src/nm-l3cfg.h +++ b/src/nm-l3cfg.h @@ -4,6 +4,7 @@ #define __NM_L3CFG_H__ #include "platform/nmp-object.h" +#include "nm-l3-config-data.h" #define NM_TYPE_L3CFG (nm_l3cfg_get_type ()) #define NM_L3CFG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_L3CFG, NML3Cfg)) @@ -15,16 +16,26 @@ #define NM_L3CFG_NETNS "netns" #define NM_L3CFG_IFINDEX "ifindex" +#define NM_L3CFG_SIGNAL_NOTIFY "l3cfg-notify" + +typedef enum { + NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED, + _NM_L3_CONFIG_NOTIFY_TYPE_NUM, +} NML3ConfigNotifyType; + +#define NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED_DETAIL "routes-temporary-not-available" + struct _NML3CfgPrivate; struct _NML3Cfg { GObject parent; struct { + struct _NML3CfgPrivate *p; NMNetns *netns; NMPlatform *platform; - int ifindex; const NMPObject *pllink; - struct _NML3CfgPrivate *p; + int ifindex; + bool changed_configs:1; } priv; }; @@ -38,6 +49,10 @@ NML3Cfg *nm_l3cfg_new (NMNetns *netns, int ifindex); void _nm_l3cfg_notify_platform_change_on_idle (NML3Cfg *self, guint32 obj_type_flags); +void _nm_l3cfg_notify_platform_change (NML3Cfg *self, + NMPlatformSignalChangeType change_type, + const NMPObject *obj); + /*****************************************************************************/ static inline int @@ -89,4 +104,52 @@ void nm_l3cfg_property_emit_unregister (NML3Cfg *self, GObject *target_obj, const GParamSpec *target_property); +/*****************************************************************************/ + +void nm_l3cfg_mark_config_dirty (NML3Cfg *self, + gconstpointer tag, + gboolean dirty); + +void nm_l3cfg_add_config (NML3Cfg *self, + gconstpointer tag, + gboolean replace_same_tag, + const NML3ConfigData *l3cfg, + int priority, + guint32 default_route_penalty_4, + guint32 default_route_penalty_6, + NML3ConfigMergeFlags merge_flags); + +void nm_l3cfg_remove_config (NML3Cfg *self, + gconstpointer tag, + const NML3ConfigData *ifcfg); + +void nm_l3cfg_remove_config_all (NML3Cfg *self, + gconstpointer tag, + gboolean only_dirty); + +/*****************************************************************************/ + +typedef enum { + /* ASSUME means to keep any pre-existing extra routes/addresses, while + * also not adding routes/addresses that are not present yet. This is to + * gracefully take over after restart, where the existing IP configuration + * should not change. */ + NM_L3_CFG_COMMIT_TYPE_ASSUME, + + /* UPDATE means to add new addresses/routes, while also removing addresses/routes + * that are no longer present (but were previously configured by NetworkManager). + * Routes/addresses that were removed externally won't be re-added, and routes/addresses + * that are added externally won't be removed. */ + NM_L3_CFG_COMMIT_TYPE_UPDATE, + + /* This is a full sync. It configures the IP addresses/routes that are indicated, + * while removing the existing ones from the interface. */ + NM_L3_CFG_COMMIT_TYPE_REAPPLY, +} NML3CfgCommitType; + +gboolean nm_l3cfg_platform_commit (NML3Cfg *self, + NML3CfgCommitType commit_type, + int addr_family, + gboolean *out_final_failure_for_temporary_not_available); + #endif /* __NM_L3CFG_H__ */ diff --git a/src/nm-netns.c b/src/nm-netns.c index 5b170c036b..cf0e5f0b6b 100644 --- a/src/nm-netns.c +++ b/src/nm-netns.c @@ -196,6 +196,7 @@ _platform_signal_cb (NMPlatform *platform, NMNetns *self = NM_NETNS (*p_self); NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE (self); const NMPObjectType obj_type = obj_type_i; + const NMPlatformSignalChangeType change_type = change_type_i; L3CfgData *l3cfg_data; l3cfg_data = g_hash_table_lookup (priv->l3cfgs, &ifindex); @@ -204,12 +205,15 @@ _platform_signal_cb (NMPlatform *platform, l3cfg_data->signal_pending_flag |= nmp_object_type_to_flags (obj_type); - if (!c_list_is_empty (&l3cfg_data->signal_pending_lst)) - return; + if (c_list_is_empty (&l3cfg_data->signal_pending_lst)) { + c_list_link_tail (&priv->l3cfg_signal_pending_lst_head, &l3cfg_data->signal_pending_lst); + if (priv->signal_pending_idle_id == 0) + priv->signal_pending_idle_id = g_idle_add (_platform_signal_on_idle_cb, self); + } - c_list_link_tail (&priv->l3cfg_signal_pending_lst_head, &l3cfg_data->signal_pending_lst); - if (priv->signal_pending_idle_id == 0) - priv->signal_pending_idle_id = g_idle_add (_platform_signal_on_idle_cb, self); + _nm_l3cfg_notify_platform_change (l3cfg_data->l3cfg, + change_type, + NMP_OBJECT_UP_CAST (platform_object)); } /*****************************************************************************/ @@ -288,6 +292,8 @@ constructed (GObject *object) g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (_platform_signal_cb), &priv->_self_signal_user_data); g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_platform_signal_cb), &priv->_self_signal_user_data); g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_platform_signal_cb), &priv->_self_signal_user_data); + g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (_platform_signal_cb), &priv->_self_signal_user_data); + g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (_platform_signal_cb), &priv->_self_signal_user_data); } NMNetns * diff --git a/src/nm-types.h b/src/nm-types.h index 6277e4d811..5d9eb67dc4 100644 --- a/src/nm-types.h +++ b/src/nm-types.h @@ -205,10 +205,15 @@ nm_link_type_supports_slaves (NMLinkType link_type) typedef enum { NMP_OBJECT_TYPE_UNKNOWN, NMP_OBJECT_TYPE_LINK, + +#define NMP_OBJECT_TYPE_IP_ADDRESS(is_ipv4) ((is_ipv4) ? NMP_OBJECT_TYPE_IP4_ADDRESS : NMP_OBJECT_TYPE_IP6_ADDRESS) NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS, + +#define NMP_OBJECT_TYPE_IP_ROUTE(is_ipv4) ((is_ipv4) ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE) NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE, + NMP_OBJECT_TYPE_ROUTING_RULE, NMP_OBJECT_TYPE_QDISC, @@ -267,6 +272,7 @@ typedef enum { /** * NMIPRouteTableSyncMode: + * @NM_IP_ROUTE_TABLE_SYNC_MODE_NONE: indicate an invalid setting. * @NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN: only the main table is synced. For all * other tables, NM won't delete any extra routes. * @NM_IP_ROUTE_TABLE_SYNC_MODE_FULL: NM will sync all tables, except the @@ -275,6 +281,7 @@ typedef enum { * local table (255). */ typedef enum { + NM_IP_ROUTE_TABLE_SYNC_MODE_NONE = 0, NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN = 1, NM_IP_ROUTE_TABLE_SYNC_MODE_FULL = 2, NM_IP_ROUTE_TABLE_SYNC_MODE_ALL = 3, diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c index 7b1f87f189..5c041cf8f3 100644 --- a/src/platform/nm-platform.c +++ b/src/platform/nm-platform.c @@ -3577,8 +3577,11 @@ _addr_array_clean_expired (int addr_family, int ifindex, GPtrArray *array, guint goto clear_and_next; } - if (!nm_utils_lifetime_get (a->timestamp, a->lifetime, a->preferred, - now, NULL)) + if (!nm_utils_lifetime_get (a->timestamp, + a->lifetime, + a->preferred, + now, + NULL)) goto clear_and_next; if (idx) { @@ -3743,168 +3746,6 @@ ip4_addr_subnets_is_secondary (const NMPObject *address, return FALSE; } -/** - * nm_platform_ip4_address_sync: - * @self: platform instance - * @ifindex: Interface index - * @known_addresses: List of addresses. The list will be modified and only - * addresses that were successfully added will be kept in the list. - * That means, expired addresses and addresses that could not be added - * will be dropped. - * Hence, the input argument @known_addresses is also an output argument - * telling which addresses were successfully added. - * Addresses are removed by unrefing the instance via nmp_object_unref() - * and leaving a NULL tombstone. - * - * A convenience function to synchronize addresses for a specific interface - * with the least possible disturbance. It simply removes addresses that are - * not listed and adds addresses that are. - * - * Returns: %TRUE on success. - */ -gboolean -nm_platform_ip4_address_sync (NMPlatform *self, - int ifindex, - GPtrArray *known_addresses) -{ - gs_unref_ptrarray GPtrArray *plat_addresses = NULL; - const NMPlatformIP4Address *known_address; - gint32 now = nm_utils_get_monotonic_timestamp_sec (); - GHashTable *plat_subnets = NULL; - GHashTable *known_subnets = NULL; - gs_unref_hashtable GHashTable *known_addresses_idx = NULL; - guint i, j, len; - NMPLookup lookup; - guint32 lifetime, preferred; - guint32 ifa_flags; - - _CHECK_SELF (self, klass, FALSE); - - if (!_addr_array_clean_expired (AF_INET, ifindex, known_addresses, now, &known_addresses_idx)) - known_addresses = NULL; - - plat_addresses = nm_platform_lookup_clone (self, - nmp_lookup_init_object (&lookup, - NMP_OBJECT_TYPE_IP4_ADDRESS, - ifindex), - NULL, NULL); - if (plat_addresses) - plat_subnets = ip4_addr_subnets_build_index (plat_addresses, TRUE, TRUE); - - /* Delete unknown addresses */ - len = plat_addresses ? plat_addresses->len : 0; - for (i = 0; i < len; i++) { - const NMPObject *plat_obj; - const NMPlatformIP4Address *plat_address; - const GPtrArray *addr_list; - - plat_obj = plat_addresses->pdata[i]; - if (!plat_obj) { - /* Already deleted */ - continue; - } - - plat_address = NMP_OBJECT_CAST_IP4_ADDRESS (plat_obj); - - if (known_addresses) { - const NMPObject *o; - - o = g_hash_table_lookup (known_addresses_idx, plat_obj); - if (o) { - gboolean secondary; - - if (!known_subnets) - known_subnets = ip4_addr_subnets_build_index (known_addresses, FALSE, FALSE); - - secondary = ip4_addr_subnets_is_secondary (o, known_subnets, known_addresses, NULL); - if (secondary == NM_FLAGS_HAS (plat_address->n_ifa_flags, IFA_F_SECONDARY)) { - /* if we have an existing known-address, with matching secondary role, - * do not delete the platform-address. */ - continue; - } - } - } - - nm_platform_ip4_address_delete (self, ifindex, - plat_address->address, - plat_address->plen, - plat_address->peer_address); - - if ( !ip4_addr_subnets_is_secondary (plat_obj, plat_subnets, plat_addresses, &addr_list) - && addr_list) { - /* If we just deleted a primary addresses and there were - * secondary ones the kernel can do two things, depending on - * version and sysctl setting: delete also secondary addresses - * or promote a secondary to primary. Ensure that secondary - * addresses are deleted, so that we can start with a clean - * slate and add addresses in the right order. */ - for (j = 1; j < addr_list->len; j++) { - const NMPObject **o; - - o = ip4_addr_subnets_addr_list_get (addr_list, j); - nm_assert (o); - - if (*o) { - const NMPlatformIP4Address *a; - - a = NMP_OBJECT_CAST_IP4_ADDRESS (*o); - nm_platform_ip4_address_delete (self, ifindex, - a->address, - a->plen, - a->peer_address); - nmp_object_unref (*o); - *o = NULL; - } - } - } - } - ip4_addr_subnets_destroy_index (plat_subnets, plat_addresses); - - if (!known_addresses) - return TRUE; - - ip4_addr_subnets_destroy_index (known_subnets, known_addresses); - - ifa_flags = nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS) - ? IFA_F_NOPREFIXROUTE - : 0; - - /* Add missing addresses */ - for (i = 0; i < known_addresses->len; i++) { - const NMPObject *o; - - o = known_addresses->pdata[i]; - if (!o) - continue; - - known_address = NMP_OBJECT_CAST_IP4_ADDRESS (o); - - lifetime = nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred, - now, &preferred); - if (!lifetime) - goto delete_and_next2; - - if (!nm_platform_ip4_address_add (self, - ifindex, - known_address->address, - known_address->plen, - known_address->peer_address, - nm_platform_ip4_broadcast_address_from_addr (known_address), - lifetime, - preferred, - ifa_flags, - known_address->label)) - goto delete_and_next2; - - continue; -delete_and_next2: - nmp_object_unref (o); - known_addresses->pdata[i] = NULL; - } - - return TRUE; -} - typedef enum { IP6_ADDR_SCOPE_LOOPBACK, IP6_ADDR_SCOPE_LINKLOCAL, @@ -3941,8 +3782,9 @@ ip6_address_scope_cmp (gconstpointer p_a, gconstpointer p_b, gpointer increasing } /** - * nm_platform_ip6_address_sync: + * nm_platform_ip_address_sync: * @self: platform instance + * @addr_family: the address family AF_INET or AF_INET6. * @ifindex: Interface index * @known_addresses: List of addresses. The list will be modified and only * addresses that were successfully added will be kept in the list. @@ -3952,7 +3794,13 @@ ip6_address_scope_cmp (gconstpointer p_a, gconstpointer p_b, gpointer increasing * telling which addresses were successfully added. * Addresses are removed by unrefing the instance via nmp_object_unref() * and leaving a NULL tombstone. - * @full_sync: Also remove link-local and temporary addresses. + * @addresses_prune: (allow-none): the list of addresses to delete. + * If platform has such an address configured, it will be deleted + * at the beginning of the sync. Note that the array will be modified + * by the function. + * Note that the addresses must be properly sorted, by their priority. + * Create this list with nm_platform_ip_address_get_prune_list() which + * gets the sorting right. * * A convenience function to synchronize addresses for a specific interface * with the least possible disturbance. It simply removes addresses that are @@ -3961,152 +3809,230 @@ ip6_address_scope_cmp (gconstpointer p_a, gconstpointer p_b, gpointer increasing * Returns: %TRUE on success. */ gboolean -nm_platform_ip6_address_sync (NMPlatform *self, - int ifindex, - GPtrArray *known_addresses, - gboolean full_sync) +nm_platform_ip_address_sync (NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray *known_addresses, + GPtrArray *addresses_prune) { - gs_unref_ptrarray GPtrArray *plat_addresses = NULL; - gint32 now = nm_utils_get_monotonic_timestamp_sec (); - guint i_plat, i_know; + const gint32 now = nm_utils_get_monotonic_timestamp_sec (); + const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family); gs_unref_hashtable GHashTable *known_addresses_idx = NULL; - NMPLookup lookup; + GPtrArray *plat_addresses; + GHashTable *known_subnets = NULL; guint32 ifa_flags; + guint i_plat; + guint i_know; + guint i; + guint j; + + _CHECK_SELF (self, klass, FALSE); /* The order we want to enforce is only among addresses with the same * scope, as the kernel keeps addresses sorted by scope. Therefore, * apply the same sorting to known addresses, so that we don't try to * unnecessary change the order of addresses with different scopes. */ - if (known_addresses) - g_ptr_array_sort_with_data (known_addresses, ip6_address_scope_cmp, GINT_TO_POINTER (TRUE)); + if (!IS_IPv4) { + if (known_addresses) + g_ptr_array_sort_with_data (known_addresses, ip6_address_scope_cmp, GINT_TO_POINTER (TRUE)); + } - if (!_addr_array_clean_expired (AF_INET6, ifindex, known_addresses, now, &known_addresses_idx)) + if (!_addr_array_clean_expired (addr_family, ifindex, known_addresses, now, &known_addresses_idx)) known_addresses = NULL; - /* @plat_addresses is in decreasing priority order (highest priority addresses first), contrary to + /* @plat_addresses must be sorted in decreasing priority order (highest priority addresses first), contrary to * @known_addresses which is in increasing priority order (lowest priority addresses first). */ - plat_addresses = nm_platform_lookup_clone (self, - nmp_lookup_init_object (&lookup, - NMP_OBJECT_TYPE_IP6_ADDRESS, - ifindex), - NULL, NULL); + plat_addresses = addresses_prune; - if (plat_addresses) { - guint known_addresses_len; - IP6AddrScope cur_scope; - gboolean delete_remaining_addrs; + if (nm_g_ptr_array_len (plat_addresses) > 0) { + /* Delete unknown addresses */ + if (IS_IPv4) { + GHashTable *plat_subnets; - g_ptr_array_sort_with_data (plat_addresses, ip6_address_scope_cmp, GINT_TO_POINTER (FALSE)); + plat_subnets = ip4_addr_subnets_build_index (plat_addresses, TRUE, TRUE); - known_addresses_len = known_addresses ? known_addresses->len : 0; + for (i = 0; i < plat_addresses->len; i++) { + const NMPObject *plat_obj; + const NMPlatformIP4Address *plat_address; + const GPtrArray *addr_list; - /* First, compare every address whether it is still a "known address", that is, whether - * to keep it or to delete it. - * - * If we don't find a matching valid address in @known_addresses, we will delete - * plat_addr. - * - * Certain addresses, like temporary addresses, are ignored by this function - * if not run with full_sync. These addresses are usually not managed by NetworkManager - * directly, or at least, they are not managed via nm_platform_ip6_address_sync(). - * Only in full_sync mode, we really want to get rid of them (usually, when we take - * the interface down). - * - * Note that we mark handled addresses by setting it to %NULL in @plat_addresses array. */ - for (i_plat = 0; i_plat < plat_addresses->len; i_plat++) { - const NMPObject *plat_obj = plat_addresses->pdata[i_plat]; - const NMPObject *know_obj; - const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS (plat_obj); - - if (NM_FLAGS_HAS (plat_addr->n_ifa_flags, IFA_F_TEMPORARY)) { - if (!full_sync) { - /* just mark as handled, without actually deleting the address. */ - goto clear_and_next; - } - } else if (known_addresses_idx) { - know_obj = g_hash_table_lookup (known_addresses_idx, plat_obj); - if ( know_obj - && plat_addr->plen == NMP_OBJECT_CAST_IP6_ADDRESS (know_obj)->plen) { - /* technically, plen is not part of the ID for IPv6 addresses and thus - * @plat_addr is essentially the same address as @know_addr (regrading - * its identity, not its other attributes). - * However, we cannot modify an existing addresses' plen without - * removing and readding it. Thus, only keep plat_addr, if the plen - * matches. - * - * keep this one, and continue */ + plat_obj = plat_addresses->pdata[i]; + if (!plat_obj) { + /* Already deleted */ continue; } - } - nm_platform_ip6_address_delete (self, ifindex, plat_addr->address, plat_addr->plen); -clear_and_next: - nmp_object_unref (g_steal_pointer (&plat_addresses->pdata[i_plat])); - } + plat_address = NMP_OBJECT_CAST_IP4_ADDRESS (plat_obj); - /* Next, we must preserve the priority of the routes. That is, source address - * selection will choose addresses in the order as they are reported by kernel. - * Note that the order in @plat_addresses of the remaining matches is highest - * priority first. - * We need to compare this to the order of addresses with same scope in - * @known_addresses (which has lowest priority first). - * - * If we find a first discrepancy, we need to delete all remaining addresses - * with same scope from that point on, because below we must re-add all the - * addresses in the right order to get their priority right. */ - cur_scope = IP6_ADDR_SCOPE_LOOPBACK; - delete_remaining_addrs = FALSE; - i_plat = plat_addresses->len; - i_know = 0; - while (i_plat > 0) { - const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS (plat_addresses->pdata[--i_plat]); - IP6AddrScope plat_scope; - - if (!plat_addr) - continue; + if (known_addresses) { + const NMPObject *o; + + o = g_hash_table_lookup (known_addresses_idx, plat_obj); + if (o) { + gboolean secondary; + + if (!known_subnets) + known_subnets = ip4_addr_subnets_build_index (known_addresses, FALSE, FALSE); - plat_scope = ip6_address_scope (plat_addr); - if (cur_scope != plat_scope) { - nm_assert (cur_scope < plat_scope); - delete_remaining_addrs = FALSE; - cur_scope = plat_scope; + secondary = ip4_addr_subnets_is_secondary (o, known_subnets, known_addresses, NULL); + if (secondary == NM_FLAGS_HAS (plat_address->n_ifa_flags, IFA_F_SECONDARY)) { + /* if we have an existing known-address, with matching secondary role, + * do not delete the platform-address. */ + continue; + } + } + } + + nm_platform_ip4_address_delete (self, + ifindex, + plat_address->address, + plat_address->plen, + plat_address->peer_address); + + if ( !ip4_addr_subnets_is_secondary (plat_obj, plat_subnets, plat_addresses, &addr_list) + && addr_list) { + /* If we just deleted a primary addresses and there were + * secondary ones the kernel can do two things, depending on + * version and sysctl setting: delete also secondary addresses + * or promote a secondary to primary. Ensure that secondary + * addresses are deleted, so that we can start with a clean + * slate and add addresses in the right order. */ + for (j = 1; j < addr_list->len; j++) { + const NMPObject **o; + + o = ip4_addr_subnets_addr_list_get (addr_list, j); + nm_assert (o); + + if (*o) { + const NMPlatformIP4Address *a; + + a = NMP_OBJECT_CAST_IP4_ADDRESS (*o); + nm_platform_ip4_address_delete (self, + ifindex, + a->address, + a->plen, + a->peer_address); + nmp_object_unref (*o); + *o = NULL; + } + } + } } + ip4_addr_subnets_destroy_index (plat_subnets, plat_addresses); + } else { + guint known_addresses_len; + IP6AddrScope cur_scope; + gboolean delete_remaining_addrs; - if (!delete_remaining_addrs) { - delete_remaining_addrs = TRUE; - for (; i_know < known_addresses_len; i_know++) { - const NMPlatformIP6Address *know_addr = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]); - IP6AddrScope know_scope; + g_ptr_array_sort_with_data (plat_addresses, ip6_address_scope_cmp, GINT_TO_POINTER (FALSE)); - if (!know_addr) - continue; + known_addresses_len = known_addresses ? known_addresses->len : 0; - know_scope = ip6_address_scope (know_addr); - if (know_scope < plat_scope) + /* First, compare every address whether it is still a "known address", that is, whether + * to keep it or to delete it. + * + * If we don't find a matching valid address in @known_addresses, we will delete + * plat_addr. + * + * Certain addresses, like temporary addresses, are ignored by this function + * if not run with full_sync. These addresses are usually not managed by NetworkManager + * directly, or at least, they are not managed via nm_platform_ip6_address_sync(). + * Only in full_sync mode, we really want to get rid of them (usually, when we take + * the interface down). + * + * Note that we mark handled addresses by setting it to %NULL in @plat_addresses array. */ + for (i_plat = 0; i_plat < plat_addresses->len; i_plat++) { + const NMPObject *plat_obj = plat_addresses->pdata[i_plat]; + const NMPObject *know_obj; + const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS (plat_obj); + + if (known_addresses_idx) { + know_obj = g_hash_table_lookup (known_addresses_idx, plat_obj); + if ( know_obj + && plat_addr->plen == NMP_OBJECT_CAST_IP6_ADDRESS (know_obj)->plen) { + /* technically, plen is not part of the ID for IPv6 addresses and thus + * @plat_addr is essentially the same address as @know_addr (regrading + * its identity, not its other attributes). + * However, we cannot modify an existing addresses' plen without + * removing and readding it. Thus, only keep plat_addr, if the plen + * matches. + * + * keep this one, and continue */ continue; - - if (IN6_ARE_ADDR_EQUAL (&plat_addr->address, &know_addr->address)) { - /* we have a match. Mark address as handled. */ - i_know++; - delete_remaining_addrs = FALSE; - goto next_plat; } - - /* plat_address has no match. Now delete_remaining_addrs is TRUE and we will - * delete all the remaining addresses with cur_scope. */ - break; } + + nm_platform_ip6_address_delete (self, ifindex, plat_addr->address, plat_addr->plen); + nmp_object_unref (g_steal_pointer (&plat_addresses->pdata[i_plat])); } - nm_platform_ip6_address_delete (self, ifindex, plat_addr->address, plat_addr->plen); + /* Next, we must preserve the priority of the routes. That is, source address + * selection will choose addresses in the order as they are reported by kernel. + * Note that the order in @plat_addresses of the remaining matches is highest + * priority first. + * We need to compare this to the order of addresses with same scope in + * @known_addresses (which has lowest priority first). + * + * If we find a first discrepancy, we need to delete all remaining addresses + * with same scope from that point on, because below we must re-add all the + * addresses in the right order to get their priority right. */ + cur_scope = IP6_ADDR_SCOPE_LOOPBACK; + delete_remaining_addrs = FALSE; + i_plat = plat_addresses->len; + i_know = 0; + while (i_plat > 0) { + const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS (plat_addresses->pdata[--i_plat]); + IP6AddrScope plat_scope; + + if (!plat_addr) + continue; + + plat_scope = ip6_address_scope (plat_addr); + if (cur_scope != plat_scope) { + nm_assert (cur_scope < plat_scope); + delete_remaining_addrs = FALSE; + cur_scope = plat_scope; + } + + if (!delete_remaining_addrs) { + delete_remaining_addrs = TRUE; + for (; i_know < known_addresses_len; i_know++) { + const NMPlatformIP6Address *know_addr = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]); + IP6AddrScope know_scope; + + if (!know_addr) + continue; + + know_scope = ip6_address_scope (know_addr); + if (know_scope < plat_scope) + continue; + + if (IN6_ARE_ADDR_EQUAL (&plat_addr->address, &know_addr->address)) { + /* we have a match. Mark address as handled. */ + i_know++; + delete_remaining_addrs = FALSE; + goto next_plat; + } + + /* plat_address has no match. Now delete_remaining_addrs is TRUE and we will + * delete all the remaining addresses with cur_scope. */ + break; + } + } + + nm_platform_ip6_address_delete (self, ifindex, plat_addr->address, plat_addr->plen); next_plat: - ; + ; + } } } if (!known_addresses) return TRUE; + if (IS_IPv4) + ip4_addr_subnets_destroy_index (known_subnets, known_addresses); + ifa_flags = nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS) ? IFA_F_NOPREFIXROUTE : 0; @@ -4115,20 +4041,51 @@ next_plat: * priority. */ for (i_know = 0; i_know < known_addresses->len; i_know++) { - const NMPlatformIP6Address *known_address = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]); - guint32 lifetime, preferred; + const NMPlatformIPXAddress *known_address; + const NMPObject *o; + guint32 lifetime; + guint32 preferred; - if (!known_address) + o = known_addresses->pdata[i_know]; + if (!o) continue; - lifetime = nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred, - now, &preferred); - - if (!nm_platform_ip6_address_add (self, ifindex, known_address->address, - known_address->plen, known_address->peer_address, - lifetime, preferred, - ifa_flags | known_address->n_ifa_flags)) - return FALSE; + nm_assert (NMP_OBJECT_GET_TYPE (o) == NMP_OBJECT_TYPE_IP_ADDRESS (IS_IPv4)); + + known_address = NMP_OBJECT_CAST_IPX_ADDRESS (o); + + lifetime = nm_utils_lifetime_get (known_address->ax.timestamp, + known_address->ax.lifetime, + known_address->ax.preferred, + now, + &preferred); + nm_assert (lifetime > 0); + + if (IS_IPv4) { + if (!nm_platform_ip4_address_add (self, + ifindex, + known_address->a4.address, + known_address->a4.plen, + known_address->a4.peer_address, + nm_platform_ip4_broadcast_address_from_addr (&known_address->a4), + lifetime, + preferred, + ifa_flags, + known_address->a4.label)) { + /* ignore error, for unclear reasons. */ + } + } else { + if (!nm_platform_ip6_address_add (self, + ifindex, + known_address->a6.address, + known_address->a6.plen, + known_address->a6.peer_address, + lifetime, + preferred, + ifa_flags + | known_address->a6.n_ifa_flags)) + return FALSE; + } } return TRUE; @@ -4193,6 +4150,49 @@ _err_inval_due_to_ipv6_tentative_pref_src (NMPlatform *self, const NMPObject *ob } GPtrArray * +nm_platform_ip_address_get_prune_list (NMPlatform *self, + int addr_family, + int ifindex, + gboolean exclude_ipv6_temporary_addrs) +{ + const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family); + const NMDedupMultiHeadEntry *head_entry; + NMPLookup lookup; + GPtrArray *result; + CList *iter; + + nmp_lookup_init_object (&lookup, + NMP_OBJECT_TYPE_IP_ADDRESS (NM_IS_IPv4 (addr_family)), + ifindex); + + head_entry = nm_platform_lookup (self, &lookup); + + if (!head_entry) + return NULL; + + result = g_ptr_array_new_full (head_entry->len, + (GDestroyNotify) nmp_object_unref); + + c_list_for_each (iter, &head_entry->lst_entries_head) { + const NMPObject *obj = c_list_entry (iter, NMDedupMultiEntry, lst_entries)->obj; + + if (!IS_IPv4) { + if ( exclude_ipv6_temporary_addrs + && NM_FLAGS_HAS (NMP_OBJECT_CAST_IP_ADDRESS (obj)->n_ifa_flags, IFA_F_TEMPORARY)) + continue; + } + + g_ptr_array_add (result, (gpointer) nmp_object_ref (obj)); + } + + if (result->len == 0) { + g_ptr_array_unref (result); + return NULL; + } + return result; +} + +GPtrArray * nm_platform_ip_route_get_prune_list (NMPlatform *self, int addr_family, int ifindex, @@ -4936,6 +4936,8 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self, nm_assert (NM_IS_PLATFORM (self)); nm_assert (ifindex > 0); + /* TODO: the blacklist should be maintained by NML3Cfg. */ + priv = NM_PLATFORM_GET_PRIVATE (self); /* first, expire all for current ifindex... */ diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h index 1a2e339100..84dcfbb4aa 100644 --- a/src/platform/nm-platform.h +++ b/src/platform/nm-platform.h @@ -1839,8 +1839,42 @@ gboolean nm_platform_ip6_address_add (NMPlatform *self, guint32 flags); gboolean nm_platform_ip4_address_delete (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address); gboolean nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen); -gboolean nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresses); -gboolean nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresses, gboolean full_sync); + +gboolean nm_platform_ip_address_sync (NMPlatform *self, + int addr_family, + int ifindex, + GPtrArray *known_addresses, + GPtrArray *addresses_prune); + +GPtrArray *nm_platform_ip_address_get_prune_list (NMPlatform *self, + int addr_family, + int ifindex, + gboolean exclude_ipv6_temporary_addrs); + +static inline gboolean +_nm_platform_ip_address_sync (NMPlatform *self, int addr_family, int ifindex, GPtrArray *known_addresses, gboolean full_sync) +{ + gs_unref_ptrarray GPtrArray *addresses_prune = NULL; + + addresses_prune = nm_platform_ip_address_get_prune_list (self, + addr_family, + ifindex, + !full_sync); + return nm_platform_ip_address_sync (self, addr_family, ifindex, known_addresses, addresses_prune); +} + +static inline gboolean +nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresses) +{ + return _nm_platform_ip_address_sync (self, AF_INET, ifindex, known_addresses, TRUE); +} + +static inline gboolean +nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresses, gboolean full_sync) +{ + return _nm_platform_ip_address_sync (self, AF_INET6, ifindex, known_addresses, full_sync); +} + gboolean nm_platform_ip_address_flush (NMPlatform *self, int addr_family, int ifindex); @@ -1848,6 +1882,18 @@ gboolean nm_platform_ip_address_flush (NMPlatform *self, void nm_platform_ip_route_normalize (int addr_family, NMPlatformIPRoute *route); +static inline gconstpointer +nm_platform_ip_route_get_gateway (int addr_family, + const NMPlatformIPRoute *route) +{ + nm_assert_addr_family (addr_family); + nm_assert (route); + + if (NM_IS_IPv4 (addr_family)) + return &((NMPlatformIP4Route *) route)->gateway; + return &((NMPlatformIP6Route *) route)->gateway; +} + int nm_platform_ip_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPObject *route); diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c index 1cd61a8e7d..615c458ab7 100644 --- a/src/platform/nmp-object.c +++ b/src/platform/nmp-object.c @@ -1570,6 +1570,25 @@ _vt_cmd_plobj_hash_update_routing_rule (const NMPlatformObject *obj, NMHashState return nm_platform_routing_rule_hash_update ((const NMPlatformRoutingRule *) obj, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL, h); } +guint +nmp_object_indirect_id_hash (gconstpointer a) +{ + const NMPObject *const*p_obj = a; + + return nmp_object_id_hash (*p_obj); +} + +gboolean +nmp_object_indirect_id_equal (gconstpointer a, gconstpointer b) +{ + const NMPObject *const*p_obj_a = a; + const NMPObject *const*p_obj_b = b; + + return nmp_object_id_equal (*p_obj_a, *p_obj_b); +} + +/*****************************************************************************/ + gboolean nmp_object_is_alive (const NMPObject *obj) { @@ -3027,6 +3046,9 @@ ASSERT_nmp_cache_is_consistent (const NMPCache *cache) /*****************************************************************************/ +/* below, ensure that addr_family get's automatically initialize to AF_UNSPEC. */ +G_STATIC_ASSERT (AF_UNSPEC == 0); + const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = { [NMP_OBJECT_TYPE_LINK - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h index c31548cb18..bf4ff42379 100644 --- a/src/platform/nmp-object.h +++ b/src/platform/nmp-object.h @@ -557,18 +557,15 @@ _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX (NMPObjectType obj_type) #define NMP_OBJECT_CAST_LNK_WIREGUARD(obj) _NMP_OBJECT_CAST (obj, lnk_wireguard, NMP_OBJECT_TYPE_LNK_WIREGUARD) static inline int +NMP_OBJECT_TYPE_TO_ADDR_FAMILY (NMPObjectType obj_type) +{ + return nmp_class_from_type (obj_type)->addr_family; +} + +static inline int NMP_OBJECT_GET_ADDR_FAMILY (const NMPObject *obj) { - switch (NMP_OBJECT_GET_TYPE (obj)) { - case NMP_OBJECT_TYPE_IP4_ADDRESS: - case NMP_OBJECT_TYPE_IP4_ROUTE: - return AF_INET; - case NMP_OBJECT_TYPE_IP6_ADDRESS: - case NMP_OBJECT_TYPE_IP6_ROUTE: - return AF_INET6; - default: - return AF_UNSPEC; - } + return NMP_OBJECT_GET_CLASS (obj)->addr_family; } static inline const NMPObject * @@ -674,6 +671,9 @@ nmp_object_id_equal (const NMPObject *obj1, const NMPObject *obj2) return nmp_object_id_cmp (obj1, obj2) == 0; } +guint nmp_object_indirect_id_hash (gconstpointer a); +gboolean nmp_object_indirect_id_equal (gconstpointer a, gconstpointer b); + gboolean nmp_object_is_alive (const NMPObject *obj); gboolean nmp_object_is_visible (const NMPObject *obj); diff --git a/src/platform/tests/test-platform-general.c b/src/platform/tests/test-platform-general.c index 86ffdbedcf..9a59a7de9f 100644 --- a/src/platform/tests/test-platform-general.c +++ b/src/platform/tests/test-platform-general.c @@ -14,6 +14,14 @@ /*****************************************************************************/ +G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Address, address)); +G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Address, address)); + +G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Route, network)); +G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Route, network)); + +/*****************************************************************************/ + static void test_init_linux_platform (void) { |