summary refs log tree commit diff
path: root/src/platform/nm-platform.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/nm-platform.c')
-rw-r--r--src/platform/nm-platform.c567
1 files changed, 430 insertions, 137 deletions
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 9274a31d..8b22ced2 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -37,7 +37,6 @@
 #include "nm-core-internal.h"
 
 #include "nm-core-utils.h"
-#include "nm-enum-types.h"
 #include "nm-platform-utils.h"
 #include "nmp-object.h"
 #include "nmp-netns.h"
@@ -79,10 +78,6 @@ G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OF
 
 /*****************************************************************************/
 
-#define NM_PLATFORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_PLATFORM, NMPlatformPrivate))
-
-G_DEFINE_TYPE (NMPlatform, nm_platform, G_TYPE_OBJECT)
-
 static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = { 0 };
 
 enum {
@@ -92,11 +87,15 @@ enum {
 	LAST_PROP,
 };
 
-typedef struct {
-	gboolean register_singleton;
+typedef struct _NMPlatformPrivate {
+	bool register_singleton:1;
 } NMPlatformPrivate;
 
-/******************************************************************/
+G_DEFINE_TYPE (NMPlatform, nm_platform, G_TYPE_OBJECT)
+
+#define NM_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMPlatform, NM_IS_PLATFORM)
+
+/*****************************************************************************/
 
 guint
 _nm_platform_signal_id_get (NMPlatformSignalIdType signal_type)
@@ -108,7 +107,7 @@ _nm_platform_signal_id_get (NMPlatformSignalIdType signal_type)
 	return signals[signal_type];
 }
 
-/******************************************************************/
+/*****************************************************************************/
 
 /* Singleton NMPlatform subclass instance and cached class object */
 NM_DEFINE_SINGLETON_INSTANCE (NMPlatform);
@@ -135,6 +134,17 @@ NM_DEFINE_SINGLETON_REGISTER (NMPlatform);
 		(void) klass; \
 	} while (0)
 
+#define _CHECK_SELF_NETNS(self, klass, netns, err_val) \
+	nm_auto_pop_netns NMPNetns *netns = NULL; \
+	NMPlatformClass *klass; \
+	do { \
+		g_return_val_if_fail (NM_IS_PLATFORM (self), err_val); \
+		klass = NM_PLATFORM_GET_CLASS (self); \
+		(void) klass; \
+		if (!nm_platform_netns_push (self, &netns)) \
+			return (err_val); \
+	} while (0)
+
 /**
  * nm_platform_setup:
  * @instance: the #NMPlatform instance
@@ -183,7 +193,7 @@ nm_platform_try_get (void)
 	return singleton_instance;
 }
 
-/******************************************************************/
+/*****************************************************************************/
 
 /**
  * _nm_platform_error_to_string:
@@ -208,7 +218,7 @@ NM_UTILS_LOOKUP_STR_DEFINE (_nm_platform_error_to_string, NMPlatformError,
 	NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_PLATFORM_ERROR_MININT),
 );
 
-/******************************************************************/
+/*****************************************************************************/
 
 gboolean
 nm_platform_check_support_kernel_extended_ifa_flags (NMPlatform *self)
@@ -253,11 +263,42 @@ nm_platform_process_events (NMPlatform *self)
 		klass->process_events (self);
 }
 
-/******************************************************************/
+/*****************************************************************************/
+
+/**
+ * nm_platform_sysctl_open_netdir:
+ * @self: platform instance
+ * @ifindex: the ifindex for which to open /sys/class/net/%s
+ * @out_ifname: optional output argument of the found ifname.
+ *
+ * Wraps nmp_utils_sysctl_open_netdir() by first changing into the right
+ * network-namespace.
+ *
+ * Returns: on success, the open file descriptor to the /sys/class/net/%s
+ *   directory.
+ */
+int
+nm_platform_sysctl_open_netdir (NMPlatform *self, int ifindex, char *out_ifname)
+{
+	const char*ifname_guess;
+	_CHECK_SELF_NETNS (self, klass, netns, -1);
+
+	g_return_val_if_fail (ifindex > 0, -1);
+
+	/* we don't have an @ifname_guess argument to make the API nicer.
+	 * But still do a cache-lookup first. Chances are good that we have
+	 * the right ifname cached and save if_indextoname() */
+	ifname_guess = nm_platform_link_get_name (self, ifindex);
+
+	return nmp_utils_sysctl_open_netdir (ifindex, ifname_guess, out_ifname);
+}
 
 /**
  * nm_platform_sysctl_set:
  * @self: platform instance
+ * @pathid: if @dirfd is present, this must be the full path that is looked up.
+ *   It is required for logging.
+ * @dirfd: optional file descriptor for parent directory for openat()
  * @path: Absolute option path
  * @value: Value to write
  *
@@ -268,14 +309,14 @@ nm_platform_process_events (NMPlatform *self)
  * Returns: %TRUE on success.
  */
 gboolean
-nm_platform_sysctl_set (NMPlatform *self, const char *path, const char *value)
+nm_platform_sysctl_set (NMPlatform *self, const char *pathid, int dirfd, const char *path, const char *value)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (path, FALSE);
 	g_return_val_if_fail (value, FALSE);
 
-	return klass->sysctl_set (self, path, value);
+	return klass->sysctl_set (self, pathid, dirfd, path, value);
 }
 
 gboolean
@@ -295,7 +336,7 @@ nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char *iface,
 		return FALSE;
 
 	path = nm_utils_ip6_property_path (iface, "hop_limit");
-	cur = nm_platform_sysctl_get_int_checked (self, path, 10, 1, G_MAXINT32, -1);
+	cur = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_ABSOLUTE (path), 10, 1, G_MAXINT32, -1);
 
 	/* only allow increasing the hop-limit to avoid DOS by an attacker
 	 * setting a low hop-limit (CVE-2015-2924, rh#1209902) */
@@ -306,7 +347,7 @@ nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char *iface,
 		char svalue[20];
 
 		sprintf (svalue, "%d", value);
-		nm_platform_sysctl_set (self, path, svalue);
+		nm_platform_sysctl_set (self, NMP_SYSCTL_PATHID_ABSOLUTE (path), svalue);
 	}
 
 	return TRUE;
@@ -315,23 +356,29 @@ nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char *iface,
 /**
  * nm_platform_sysctl_get:
  * @self: platform instance
+ * @dirfd: if non-negative, used to lookup the path via openat().
+ * @pathid: if @dirfd is present, this must be the full path that is looked up.
+ *   It is required for logging.
  * @path: Absolute path to sysctl
  *
  * Returns: (transfer full): Contents of the virtual sysctl file.
  */
 char *
-nm_platform_sysctl_get (NMPlatform *self, const char *path)
+nm_platform_sysctl_get (NMPlatform *self, const char *pathid, int dirfd, const char *path)
 {
 	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (path, NULL);
 
-	return klass->sysctl_get (self, path);
+	return klass->sysctl_get (self, pathid, dirfd, path);
 }
 
 /**
  * nm_platform_sysctl_get_int32:
  * @self: platform instance
+ * @pathid: if @dirfd is present, this must be the full path that is looked up.
+ *   It is required for logging.
+ * @dirfd: if non-negative, used to lookup the path via openat().
  * @path: Absolute path to sysctl
  * @fallback: default value, if the content of path could not be read
  * as decimal integer.
@@ -341,14 +388,17 @@ nm_platform_sysctl_get (NMPlatform *self, const char *path)
  * value, on success %errno will be set to zero.
  */
 gint32
-nm_platform_sysctl_get_int32 (NMPlatform *self, const char *path, gint32 fallback)
+nm_platform_sysctl_get_int32 (NMPlatform *self, const char *pathid, int dirfd, const char *path, gint32 fallback)
 {
-	return nm_platform_sysctl_get_int_checked (self, path, 10, G_MININT32, G_MAXINT32, fallback);
+	return nm_platform_sysctl_get_int_checked (self, pathid, dirfd, path, 10, G_MININT32, G_MAXINT32, fallback);
 }
 
 /**
  * nm_platform_sysctl_get_int_checked:
  * @self: platform instance
+ * @pathid: if @dirfd is present, this must be the full path that is looked up.
+ *   It is required for logging.
+ * @dirfd: if non-negative, used to lookup the path via openat().
  * @path: Absolute path to sysctl
  * @base: base of numeric conversion
  * @min: minimal value that is still valid
@@ -363,7 +413,7 @@ nm_platform_sysctl_get_int32 (NMPlatform *self, const char *path, gint32 fallbac
  * (inclusive) or @fallback.
  */
 gint64
-nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *path, guint base, gint64 min, gint64 max, gint64 fallback)
+nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *pathid, int dirfd, const char *path, guint base, gint64 min, gint64 max, gint64 fallback)
 {
 	char *value = NULL;
 	gint32 ret;
@@ -373,7 +423,7 @@ nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *path, guint ba
 	g_return_val_if_fail (path, fallback);
 
 	if (path)
-		value = nm_platform_sysctl_get (self, path);
+		value = nm_platform_sysctl_get (self, pathid, dirfd, path);
 
 	if (!value) {
 		errno = EINVAL;
@@ -385,7 +435,7 @@ nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *path, guint ba
 	return ret;
 }
 
-/******************************************************************/
+/*****************************************************************************/
 
 static int
 _link_get_all_presort (gconstpointer  p_a,
@@ -720,6 +770,22 @@ nm_platform_link_get_ifindex (NMPlatform *self, const char *name)
 	return pllink ? pllink->ifindex : 0;
 }
 
+const char *
+nm_platform_if_indextoname (NMPlatform *self, int ifindex, char *out_ifname/* of size IFNAMSIZ */)
+{
+	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
+
+	return nmp_utils_if_indextoname (ifindex, out_ifname);
+}
+
+int
+nm_platform_if_nametoindex (NMPlatform *self, const char *ifname)
+{
+	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
+
+	return nmp_utils_if_nametoindex (ifname);
+}
+
 /**
  * nm_platform_link_get_name:
  * @self: platform instance
@@ -1461,6 +1527,12 @@ nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLi
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_IPIP, out_link);
 }
 
+const NMPlatformLnkMacsec *
+nm_platform_link_get_lnk_macsec (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_MACSEC, out_link);
+}
+
 const NMPlatformLnkMacvlan *
 nm_platform_link_get_lnk_macvlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
 {
@@ -1658,34 +1730,44 @@ nm_platform_link_tun_add (NMPlatform *self,
 
 /*****************************************************************************/
 
-static char *
-link_option_path (NMPlatform *self, int master, const char *category, const char *option)
+static gboolean
+link_set_option (NMPlatform *self, int ifindex, const char *category, const char *option, const char *value)
 {
-	const char *name = nm_platform_link_get_name (self, master);
-
-	if (!name || !category || !option)
-		return NULL;
+	nm_auto_close int dirfd = -1;
+	char ifname_verified[IFNAMSIZ];
+	const char *path;
 
-	return g_strdup_printf ("/sys/class/net/%s/%s/%s",
-	                        NM_ASSERT_VALID_PATH_COMPONENT (name),
-	                        NM_ASSERT_VALID_PATH_COMPONENT (category),
-	                        NM_ASSERT_VALID_PATH_COMPONENT (option));
-}
+	if (!category || !option)
+		return FALSE;
 
-static gboolean
-link_set_option (NMPlatform *self, int master, const char *category, const char *option, const char *value)
-{
-	gs_free char *path = link_option_path (self, master, category, option);
+	dirfd = nm_platform_sysctl_open_netdir (self, ifindex, ifname_verified);
+	if (dirfd < 0)
+		return FALSE;
 
-	return path && nm_platform_sysctl_set (self, path, value);
+	path = nm_sprintf_bufa (strlen (category) + strlen (option) + 2,
+	                        "%s/%s",
+	                        category, option);
+	return nm_platform_sysctl_set (self, NMP_SYSCTL_PATHID_NETDIR_unsafe (dirfd, ifname_verified, path), value);
 }
 
 static char *
-link_get_option (NMPlatform *self, int master, const char *category, const char *option)
+link_get_option (NMPlatform *self, int ifindex, const char *category, const char *option)
 {
-	gs_free char *path = link_option_path (self, master, category, option);
+	nm_auto_close int dirfd = -1;
+	char ifname_verified[IFNAMSIZ];
+	const char *path;
 
-	return path ? nm_platform_sysctl_get (self, path) : NULL;
+	if (!category || !option)
+		return NULL;
+
+	dirfd = nm_platform_sysctl_open_netdir (self, ifindex, ifname_verified);
+	if (dirfd < 0)
+		return NULL;
+
+	path = nm_sprintf_bufa (strlen (category) + strlen (option) + 2,
+	                        "%s/%s",
+	                        category, option);
+	return nm_platform_sysctl_get (self, NMP_SYSCTL_PATHID_NETDIR_unsafe (dirfd, ifname_verified, path));
 }
 
 static const char *
@@ -1763,7 +1845,7 @@ nm_platform_sysctl_slave_get_option (NMPlatform *self, int ifindex, const char *
 	return link_get_option (self, ifindex, slave_category (self, ifindex), option);
 }
 
-/******************************************************************************/
+/*****************************************************************************/
 
 gboolean
 nm_platform_link_vlan_change (NMPlatform *self,
@@ -1963,10 +2045,11 @@ nm_platform_link_infiniband_get_properties (NMPlatform *self,
                                             int *out_p_key,
                                             const char **out_mode)
 {
+	nm_auto_close int dirfd = -1;
+	char ifname_verified[IFNAMSIZ];
 	const NMPlatformLnkInfiniband *plnk;
 	const NMPlatformLink *plink;
-	const char *iface;
-	char *path, *contents;
+	char *contents;
 	const char *mode;
 	int p_key = 0;
 
@@ -1990,15 +2073,13 @@ nm_platform_link_infiniband_get_properties (NMPlatform *self,
 	/* Could not get the link information via netlink. To support older kernels,
 	 * fallback to reading sysfs. */
 
-	iface = NM_ASSERT_VALID_PATH_COMPONENT (plink->name);
+	dirfd = nm_platform_sysctl_open_netdir (self, ifindex, ifname_verified);
+	if (dirfd < 0)
+		return FALSE;
 
-	/* Fall back to reading sysfs */
-	path = g_strdup_printf ("/sys/class/net/%s/mode", iface);
-	contents = nm_platform_sysctl_get (self, path);
-	g_free (path);
+	contents = nm_platform_sysctl_get (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname_verified, "mode"));
 	if (!contents)
 		return FALSE;
-
 	if (strstr (contents, "datagram"))
 		mode = "datagram";
 	else if (strstr (contents, "connected"))
@@ -2007,13 +2088,7 @@ nm_platform_link_infiniband_get_properties (NMPlatform *self,
 		mode = NULL;
 	g_free (contents);
 
-	path = g_strdup_printf ("/sys/class/net/%s/pkey", iface);
-	contents = nm_platform_sysctl_get (self, path);
-	g_free (path);
-	if (!contents)
-		return FALSE;
-	p_key = (int) _nm_utils_ascii_str_to_int64 (contents, 16, 0, 0xFFFF, -1);
-	g_free (contents);
+	p_key = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname_verified, "pkey"), 16, 0, 0xFFFF, -1);
 	if (p_key < 0)
 		return FALSE;
 
@@ -2102,6 +2177,43 @@ nm_platform_link_ipip_add (NMPlatform *self,
 }
 
 /**
+ * nm_platform_macsec_add:
+ * @self: platform instance
+ * @name: name of the new interface
+ * @props: interface properties
+ * @out_link: on success, the link object
+ *
+ * Create a MACsec interface.
+ */
+NMPlatformError
+nm_platform_link_macsec_add (NMPlatform *self,
+                             const char *name,
+                             int parent,
+                             const NMPlatformLnkMacsec *props,
+                             const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_MACSEC, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD ("adding macsec '%s' parent %u sci %llx",
+	       name,
+	       parent,
+	       (unsigned long long) props->sci);
+
+	if (!klass->link_macsec_add (self, name, parent, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+/**
  * nm_platform_macvlan_add:
  * @self: platform instance
  * @name: name of the new interface
@@ -2206,7 +2318,7 @@ nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_pe
 
 		if (!nm_platform_netns_push (self, &netns))
 			return FALSE;
-		peer_ifindex = nmp_utils_ethtool_get_peer_ifindex (plink->name);
+		peer_ifindex = nmp_utils_ethtool_get_peer_ifindex (plink->ifindex);
 		if (peer_ifindex <= 0)
 			return FALSE;
 
@@ -2216,57 +2328,39 @@ nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_pe
 }
 
 gboolean
-nm_platform_link_tun_get_properties_ifname (NMPlatform *self, const char *ifname, NMPlatformTunProperties *props)
+nm_platform_link_tun_get_properties (NMPlatform *self, int ifindex, NMPlatformTunProperties *props)
 {
-	char path[256];
-	char *val;
+	nm_auto_close int dirfd = -1;
+	char ifname[IFNAMSIZ];
+	gint64 flags;
 	gboolean success = TRUE;
-
 	_CHECK_SELF (self, klass, FALSE);
 
+	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (props, FALSE);
 
 	memset (props, 0, sizeof (*props));
 	props->owner = -1;
 	props->group = -1;
 
-	if (!ifname || !nm_utils_iface_valid_name (ifname))
+	dirfd = nm_platform_sysctl_open_netdir (self, ifindex, ifname);
+	if (dirfd < 0)
 		return FALSE;
 
-	nm_sprintf_buf (path, "/sys/class/net/%s/owner", ifname);
-	val = nm_platform_sysctl_get (self, path);
-	if (val) {
-		props->owner = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
-		if (errno)
-			success = FALSE;
-		g_free (val);
-	} else
+	props->owner = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "owner"), 10, -1, G_MAXINT64, -1);
+	if (errno)
 		success = FALSE;
 
-	nm_sprintf_buf (path, "/sys/class/net/%s/group", ifname);
-	val = nm_platform_sysctl_get (self, path);
-	if (val) {
-		props->group = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
-		if (errno)
-			success = FALSE;
-		g_free (val);
-	} else
+	props->group = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "group"), 10, -1, G_MAXINT64, -1);
+	if (errno)
 		success = FALSE;
 
-	nm_sprintf_buf (path, "/sys/class/net/%s/tun_flags", ifname);
-	val = nm_platform_sysctl_get (self, path);
-	if (val) {
-		gint64 flags;
-
-		flags = _nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0);
-		if (!errno) {
-			props->mode = ((flags & (IFF_TUN | IFF_TAP)) == IFF_TUN) ? "tun" : "tap";
-			props->no_pi = !!(flags & IFF_NO_PI);
-			props->vnet_hdr = !!(flags & IFF_VNET_HDR);
-			props->multi_queue = !!(flags & NM_IFF_MULTI_QUEUE);
-		} else
-			success = FALSE;
-		g_free (val);
+	flags = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "tun_flags"), 16, 0, G_MAXINT64, -1);
+	if (flags >= 0) {
+		props->mode = ((flags & (IFF_TUN | IFF_TAP)) == IFF_TUN) ? "tun" : "tap";
+		props->no_pi = !!(flags & IFF_NO_PI);
+		props->vnet_hdr = !!(flags & IFF_VNET_HDR);
+		props->multi_queue = !!(flags & NM_IFF_MULTI_QUEUE);
 	} else
 		success = FALSE;
 
@@ -2274,17 +2368,6 @@ nm_platform_link_tun_get_properties_ifname (NMPlatform *self, const char *ifname
 }
 
 gboolean
-nm_platform_link_tun_get_properties (NMPlatform *self, int ifindex, NMPlatformTunProperties *props)
-{
-	_CHECK_SELF (self, klass, FALSE);
-
-	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (props != NULL, FALSE);
-
-	return nm_platform_link_tun_get_properties_ifname (self, nm_platform_link_get_name (self, ifindex), props);
-}
-
-gboolean
 nm_platform_wifi_get_capabilities (NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps)
 {
 	_CHECK_SELF (self, klass, FALSE);
@@ -2466,33 +2549,39 @@ _to_string_ifa_flags (guint32 ifa_flags, char *buf, gsize size)
 	return buf;
 }
 
-/******************************************************************/
+/*****************************************************************************/
 
 gboolean
-nm_platform_ethtool_set_wake_on_lan (NMPlatform *self, const char *ifname, NMSettingWiredWakeOnLan wol, const char *wol_password)
+nm_platform_ethtool_set_wake_on_lan (NMPlatform *self, int ifindex, NMSettingWiredWakeOnLan wol, const char *wol_password)
 {
-	nm_auto_pop_netns NMPNetns *netns = NULL;
-	_CHECK_SELF (self, klass, FALSE);
+	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
 
-	if (!nm_platform_netns_push (self, &netns))
-		return FALSE;
+	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	return nmp_utils_ethtool_set_wake_on_lan (ifname, wol, wol_password);
+	return nmp_utils_ethtool_set_wake_on_lan (ifindex, wol, wol_password);
 }
 
 gboolean
-nm_platform_ethtool_get_link_speed (NMPlatform *self, const char *ifname, guint32 *out_speed)
+nm_platform_ethtool_set_link_settings (NMPlatform *self, int ifindex, gboolean autoneg, guint32 speed, NMPlatformLinkDuplexType duplex)
 {
-	nm_auto_pop_netns NMPNetns *netns = NULL;
-	_CHECK_SELF (self, klass, FALSE);
+	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
 
-	if (!nm_platform_netns_push (self, &netns))
-		return FALSE;
+	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	return nmp_utils_ethtool_get_link_speed (ifname, out_speed);
+	return nmp_utils_ethtool_set_link_settings (ifindex, autoneg, speed, duplex);
 }
 
-/******************************************************************/
+gboolean
+nm_platform_ethtool_get_link_settings (NMPlatform *self, int ifindex, gboolean *out_autoneg, guint32 *out_speed,  NMPlatformLinkDuplexType *out_duplex)
+{
+	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return nmp_utils_ethtool_get_link_settings (ifindex, out_autoneg, out_speed, out_duplex);
+}
+
+/*****************************************************************************/
 
 void
 nm_platform_ip4_address_set_addr (NMPlatformIP4Address *addr, in_addr_t address, guint8 plen)
@@ -2662,14 +2751,14 @@ nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr addr
 	return klass->ip6_address_get (self, ifindex, address, plen);
 }
 
-static gboolean
+static const NMPlatformIP4Address *
 array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address *address, gint32 now)
 {
 	guint len = addresses ? addresses->len : 0;
 	guint i;
 
 	for (i = 0; i < len; i++) {
-		NMPlatformIP4Address *candidate = &g_array_index (addresses, NMPlatformIP4Address, i);
+		const NMPlatformIP4Address *candidate = &g_array_index (addresses, NMPlatformIP4Address, i);
 
 		if (   candidate->address == address->address
 		    && candidate->plen == address->plen
@@ -2678,11 +2767,11 @@ array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address
 
 			if (nm_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
 			                           now, &lifetime, &preferred))
-				return TRUE;
+				return candidate;
 		}
 	}
 
-	return FALSE;
+	return NULL;
 }
 
 static gboolean
@@ -2706,6 +2795,97 @@ array_contains_ip6_address (const GArray *addresses, const NMPlatformIP6Address
 	return FALSE;
 }
 
+static gboolean
+_ptr_inside_ip4_addr_array (const GArray *array, gconstpointer needle)
+{
+	return    needle >= (gconstpointer) &g_array_index (array, const NMPlatformIP4Address, 0)
+	       && needle <  (gconstpointer) &g_array_index (array, const NMPlatformIP4Address, array->len);
+}
+
+static void
+ip4_addr_subnets_destroy_index (GHashTable *ht, const GArray *addresses)
+{
+	GHashTableIter iter;
+	gpointer p;
+
+	g_hash_table_iter_init (&iter, ht);
+
+	while (g_hash_table_iter_next (&iter, NULL, &p)) {
+		if (!_ptr_inside_ip4_addr_array (addresses, p)) {
+			g_ptr_array_free ((GPtrArray *) p, TRUE);
+		}
+	}
+
+	g_hash_table_unref (ht);
+}
+
+static GHashTable *
+ip4_addr_subnets_build_index (const GArray *addresses, gboolean consider_flags)
+{
+	const NMPlatformIP4Address *address;
+	gpointer p;
+	GHashTable *subnets;
+	GPtrArray *ptr;
+	guint32 net;
+	guint i;
+	gint position;
+
+	if (!addresses)
+		return NULL;
+
+	subnets = g_hash_table_new_full (g_direct_hash,
+	                                 g_direct_equal,
+	                                 NULL,
+	                                 NULL);
+
+	/* Build a hash table of all addresses per subnet */
+	for (i = 0; i < addresses->len; i++) {
+		address = &g_array_index (addresses, const NMPlatformIP4Address, i);
+		net = address->address & nm_utils_ip4_prefix_to_netmask (address->plen);
+		if (!g_hash_table_lookup_extended (subnets, GUINT_TO_POINTER (net), NULL, &p)) {
+			g_hash_table_insert (subnets, GUINT_TO_POINTER (net), (gpointer) address);
+			continue;
+		}
+		if (_ptr_inside_ip4_addr_array (addresses, p)) {
+			ptr = g_ptr_array_new ();
+			g_hash_table_insert (subnets, GUINT_TO_POINTER (net), ptr);
+			g_ptr_array_add (ptr, p);
+		} else
+			ptr = p;
+
+		if (!consider_flags || NM_FLAGS_HAS (address->n_ifa_flags, IFA_F_SECONDARY))
+			position = -1; /* append */
+		else
+			position = 0; /* prepend */
+
+		g_ptr_array_insert (ptr, position, (gpointer) address);
+	}
+
+	return subnets;
+}
+
+static gboolean
+ip4_addr_subnets_is_secondary (const NMPlatformIP4Address *address, GHashTable *subnets, const GArray *addresses, GPtrArray **out_addr_list)
+{
+	GPtrArray *addr_list;
+	gpointer p;
+	guint32 net;
+
+	net = address->address & nm_utils_ip4_prefix_to_netmask (address->plen);
+	p = g_hash_table_lookup (subnets, GUINT_TO_POINTER (net));
+	nm_assert (p);
+	if (!_ptr_inside_ip4_addr_array (addresses, p)) {
+		addr_list = p;
+		if (addr_list->pdata[0] != address) {
+			NM_SET_OUT (out_addr_list, addr_list);
+			return TRUE;
+		}
+	} else
+		nm_assert ((gconstpointer) address == p);
+	NM_SET_OUT (out_addr_list, NULL);
+	return FALSE;
+}
+
 /**
  * nm_platform_ip4_address_sync:
  * @self: platform instance
@@ -2727,19 +2907,64 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
 {
 	GArray *addresses;
 	NMPlatformIP4Address *address;
+	const NMPlatformIP4Address *known_address;
 	gint32 now = nm_utils_get_monotonic_timestamp_s ();
-	int i;
+	GHashTable *plat_subnets;
+	GHashTable *known_subnets;
+	GPtrArray *ptr;
+	int i, j;
 
 	_CHECK_SELF (self, klass, FALSE);
 
-	/* Delete unknown addresses */
 	addresses = nm_platform_ip4_address_get_all (self, ifindex);
+	plat_subnets = ip4_addr_subnets_build_index (addresses, TRUE);
+	known_subnets = ip4_addr_subnets_build_index (known_addresses, FALSE);
+
+	/* Delete unknown addresses */
 	for (i = 0; i < addresses->len; i++) {
 		address = &g_array_index (addresses, NMPlatformIP4Address, i);
 
-		if (!array_contains_ip4_address (known_addresses, address, now))
-			nm_platform_ip4_address_delete (self, ifindex, address->address, address->plen, address->peer_address);
+		if (!address->ifindex) {
+			/* Already deleted */
+			continue;
+		}
+
+		known_address = array_contains_ip4_address (known_addresses, address, now);
+		if (known_address) {
+			gboolean secondary;
+
+			secondary = ip4_addr_subnets_is_secondary (known_address, known_subnets, known_addresses, NULL);
+			/* Ignore the matching address if it has a different primary/slave
+			 * role. */
+			if (secondary != NM_FLAGS_HAS (address->n_ifa_flags, IFA_F_SECONDARY))
+				known_address = NULL;
+		}
+
+		if (!known_address) {
+			nm_platform_ip4_address_delete (self, ifindex,
+			                                address->address,
+			                                address->plen,
+			                                address->peer_address);
+			if (   !ip4_addr_subnets_is_secondary (address, plat_subnets, addresses, &ptr)
+			    && ptr) {
+				/* 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 < ptr->len; j++) {
+					address = ptr->pdata[j];
+					nm_platform_ip4_address_delete (self, ifindex,
+					                                address->address,
+					                                address->plen,
+					                                address->peer_address);
+					address->ifindex = 0;
+				}
+			}
+		}
 	}
+	ip4_addr_subnets_destroy_index (plat_subnets, addresses);
 	g_array_free (addresses, TRUE);
 
 	if (out_added_addresses)
@@ -2750,17 +2975,20 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
 
 	/* Add missing addresses */
 	for (i = 0; i < known_addresses->len; i++) {
-		const NMPlatformIP4Address *known_address = &g_array_index (known_addresses, NMPlatformIP4Address, i);
 		guint32 lifetime, preferred;
 
+		known_address = &g_array_index (known_addresses, NMPlatformIP4Address, i);
+
 		if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
 		                            now, &lifetime, &preferred))
 			continue;
 
 		if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->plen,
 		                                  known_address->peer_address, lifetime, preferred,
-		                                  0, known_address->label))
+		                                  0, known_address->label)) {
+			ip4_addr_subnets_destroy_index (known_subnets, known_addresses);
 			return FALSE;
+		}
 
 		if (out_added_addresses) {
 			if (!*out_added_addresses)
@@ -2769,6 +2997,8 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
 		}
 	}
 
+	ip4_addr_subnets_destroy_index (known_subnets, known_addresses);
+
 	return TRUE;
 }
 
@@ -2815,6 +3045,11 @@ nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GArray *known
 		const NMPlatformIP6Address *known_address = &g_array_index (known_addresses, NMPlatformIP6Address, i);
 		guint32 lifetime, preferred;
 
+		if (NM_FLAGS_HAS (known_address->n_ifa_flags, IFA_F_TEMPORARY)) {
+			/* Kernel manages these */
+			continue;
+		}
+
 		if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
 		                            now, &lifetime, &preferred))
 			continue;
@@ -2837,7 +3072,7 @@ nm_platform_address_flush (NMPlatform *self, int ifindex)
 	       && nm_platform_ip6_address_sync (self, ifindex, NULL, FALSE);
 }
 
-/******************************************************************/
+/*****************************************************************************/
 
 GArray *
 nm_platform_ip4_route_get_all (NMPlatform *self, int ifindex, NMPlatformGetRouteFlags flags)
@@ -2985,7 +3220,7 @@ nm_platform_ip6_route_get (NMPlatform *self, int ifindex, struct in6_addr networ
 	return klass->ip6_route_get (self, ifindex, network, plen, metric);
 }
 
-/******************************************************************/
+/*****************************************************************************/
 
 const char *
 nm_platform_vlan_qos_mapping_to_string (const char *name,
@@ -3278,6 +3513,39 @@ nm_platform_lnk_ipip_to_string (const NMPlatformLnkIpIp *lnk, char *buf, gsize l
 }
 
 const char *
+nm_platform_lnk_macsec_to_string (const NMPlatformLnkMacsec *lnk, char *buf, gsize len)
+{
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "macsec "
+	            "sci %016llx "
+	            "protect %s "
+	            "cipher %016llx "
+	            "icvlen %u "
+	            "encodingsa %u "
+	            "validate %u "
+	            "encrypt %s "
+	            "send_sci %s "
+	            "end_station %s "
+	            "scb %s "
+	            "replay %s",
+	            (unsigned long long) lnk->sci,
+	            lnk->protect ? "on" : "off",
+	            (unsigned long long) lnk->cipher_suite,
+	            lnk->icv_length,
+	            lnk->encoding_sa,
+	            lnk->validation,
+	            lnk->encrypt ? "on" : "off",
+	            lnk->include_sci ? "on" : "off",
+	            lnk->es ? "on" : "off",
+	            lnk->scb ? "on" : "off",
+	            lnk->replay_protect ? "on" : "off");
+	return buf;
+}
+
+const char *
 nm_platform_lnk_macvlan_to_string (const NMPlatformLnkMacvlan *lnk, char *buf, gsize len)
 {
 	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
@@ -3859,6 +4127,25 @@ nm_platform_lnk_ipip_cmp (const NMPlatformLnkIpIp *a, const NMPlatformLnkIpIp *b
 }
 
 int
+nm_platform_lnk_macsec_cmp (const NMPlatformLnkMacsec *a, const NMPlatformLnkMacsec *b)
+{
+	_CMP_SELF (a, b);
+	_CMP_FIELD (a, b, sci);
+	_CMP_FIELD (a, b, icv_length);
+	_CMP_FIELD (a, b, cipher_suite);
+	_CMP_FIELD (a, b, window);
+	_CMP_FIELD (a, b, encoding_sa);
+	_CMP_FIELD (a, b, validation);
+	_CMP_FIELD (a, b, encrypt);
+	_CMP_FIELD (a, b, protect);
+	_CMP_FIELD (a, b, include_sci);
+	_CMP_FIELD (a, b, es);
+	_CMP_FIELD (a, b, scb);
+	_CMP_FIELD (a, b, replay_protect);
+	return 0;
+}
+
+int
 nm_platform_lnk_macvlan_cmp (const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b)
 {
 	_CMP_SELF (a, b);
@@ -4082,7 +4369,7 @@ log_ip6_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatform
 	_LOGD ("signal: route   6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_route_to_string (route, NULL, 0));
 }
 
-/******************************************************************/
+/*****************************************************************************/
 
 NMPNetns *
 nm_platform_netns_get (NMPlatform *self)
@@ -4107,7 +4394,7 @@ nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns)
 	return TRUE;
 }
 
-/******************************************************************/
+/*****************************************************************************/
 
 static gboolean
 _vtr_v4_route_add (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route, gint64 metric)
@@ -4174,7 +4461,7 @@ _vtr_v6_route_delete_default (NMPlatform *self, int ifindex, guint32 metric)
 	return nm_platform_ip6_route_delete (self, ifindex, in6addr_any, 0, metric);
 }
 
-/******************************************************************/
+/*****************************************************************************/
 
 const NMPlatformVTableRoute nm_platform_vtable_route_v4 = {
 	.is_ip4                         = TRUE,
@@ -4202,7 +4489,7 @@ const NMPlatformVTableRoute nm_platform_vtable_route_v6 = {
 	.metric_normalize               = nm_utils_ip6_route_metric_normalize,
 };
 
-/******************************************************************/
+/*****************************************************************************/
 
 static void
 set_property (GObject *object, guint prop_id,
@@ -4245,8 +4532,9 @@ constructed (GObject *object)
 }
 
 static void
-nm_platform_init (NMPlatform *object)
+nm_platform_init (NMPlatform *self)
 {
+	self->_priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_PLATFORM, NMPlatformPrivate);
 }
 
 static void
@@ -4294,7 +4582,12 @@ nm_platform_class_init (NMPlatformClass *platform_class)
 			                            G_SIGNAL_RUN_FIRST, \
 			                            G_CALLBACK (method), \
 			                            NULL, NULL, NULL, \
-			                            G_TYPE_NONE, 4, NM_TYPE_POBJECT_TYPE, G_TYPE_INT, G_TYPE_POINTER, NM_TYPE_PLATFORM_SIGNAL_CHANGE_TYPE); \
+			                            G_TYPE_NONE, 4, \
+			                            G_TYPE_INT, /* (int) NMPObjectType */ \
+			                            G_TYPE_INT, /* ifindex */ \
+			                            G_TYPE_POINTER /* const NMPObject * */, \
+			                            G_TYPE_INT /* (int) NMPlatformSignalChangeType */ \
+			                            ); \
 	} G_STMT_END
 
 	/* Signals */