summary refs log tree commit diff
path: root/src/platform
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-10-20 01:30:31 +0200
committerMichael Biebl <biebl@debian.org>2018-10-20 01:30:31 +0200
commit6518e361171f64bcaaa4bf868139362ed95cc2e0 (patch)
treed2d5b53faf80646a40ec2c0c7f2a42b3959612f5 /src/platform
parente126f3e804c35480c4f075777430419d6ece23da (diff)
New upstream version 1.14.2 upstream/1.14.2
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/linux/nl802154.h452
-rw-r--r--src/platform/nm-fake-platform.c14
-rw-r--r--src/platform/nm-linux-platform.c1628
-rw-r--r--src/platform/nm-netlink.c360
-rw-r--r--src/platform/nm-netlink.h70
-rw-r--r--src/platform/nm-platform-utils.c737
-rw-r--r--src/platform/nm-platform-utils.h56
-rw-r--r--src/platform/nm-platform.c521
-rw-r--r--src/platform/nm-platform.h106
-rw-r--r--src/platform/nmp-object.c351
-rw-r--r--src/platform/nmp-object.h51
-rw-r--r--src/platform/tests/test-cleanup.c2
-rw-r--r--src/platform/tests/test-common.c65
-rw-r--r--src/platform/tests/test-common.h6
-rw-r--r--src/platform/tests/test-general.c4
-rw-r--r--src/platform/tests/test-link.c243
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.c (renamed from src/platform/wifi/wifi-utils-nl80211.c)252
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.h (renamed from src/platform/wifi/wifi-utils-nl80211.h)15
-rw-r--r--src/platform/wifi/nm-wifi-utils-private.h (renamed from src/platform/wifi/wifi-utils-private.h)48
-rw-r--r--src/platform/wifi/nm-wifi-utils-wext.c (renamed from src/platform/wifi/wifi-utils-wext.c)139
-rw-r--r--src/platform/wifi/nm-wifi-utils-wext.h (renamed from src/platform/wifi/wifi-utils-wext.h)17
-rw-r--r--src/platform/wifi/nm-wifi-utils.c240
-rw-r--r--src/platform/wifi/nm-wifi-utils.h84
-rw-r--r--src/platform/wifi/wifi-utils.c231
-rw-r--r--src/platform/wifi/wifi-utils.h79
-rw-r--r--src/platform/wpan/nm-wpan-utils.c286
-rw-r--r--src/platform/wpan/nm-wpan-utils.h47
27 files changed, 4728 insertions, 1376 deletions
diff --git a/src/platform/linux/nl802154.h b/src/platform/linux/nl802154.h
new file mode 100644
index 00000000..ddcee128
--- /dev/null
+++ b/src/platform/linux/nl802154.h
@@ -0,0 +1,452 @@
+#ifndef __NL802154_H
+#define __NL802154_H
+/*
+ * 802.15.4 netlink interface public header
+ *
+ * Copyright 2014 Alexander Aring <aar@pengutronix.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#define NL802154_GENL_NAME "nl802154"
+
+enum nl802154_commands {
+/* don't change the order or add anything between, this is ABI! */
+/* currently we don't shipping this file via uapi, ignore the above one */
+	NL802154_CMD_UNSPEC,
+
+	NL802154_CMD_GET_WPAN_PHY,		/* can dump */
+	NL802154_CMD_SET_WPAN_PHY,
+	NL802154_CMD_NEW_WPAN_PHY,
+	NL802154_CMD_DEL_WPAN_PHY,
+
+	NL802154_CMD_GET_INTERFACE,		/* can dump */
+	NL802154_CMD_SET_INTERFACE,
+	NL802154_CMD_NEW_INTERFACE,
+	NL802154_CMD_DEL_INTERFACE,
+
+	NL802154_CMD_SET_CHANNEL,
+
+	NL802154_CMD_SET_PAN_ID,
+	NL802154_CMD_SET_SHORT_ADDR,
+
+	NL802154_CMD_SET_TX_POWER,
+	NL802154_CMD_SET_CCA_MODE,
+	NL802154_CMD_SET_CCA_ED_LEVEL,
+
+	NL802154_CMD_SET_MAX_FRAME_RETRIES,
+
+	NL802154_CMD_SET_BACKOFF_EXPONENT,
+	NL802154_CMD_SET_MAX_CSMA_BACKOFFS,
+
+	NL802154_CMD_SET_LBT_MODE,
+
+	NL802154_CMD_SET_ACKREQ_DEFAULT,
+
+	NL802154_CMD_SET_WPAN_PHY_NETNS,
+
+	/* add new commands above here */
+
+#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
+	NL802154_CMD_SET_SEC_PARAMS,
+	NL802154_CMD_GET_SEC_KEY,		/* can dump */
+	NL802154_CMD_NEW_SEC_KEY,
+	NL802154_CMD_DEL_SEC_KEY,
+	NL802154_CMD_GET_SEC_DEV,		/* can dump */
+	NL802154_CMD_NEW_SEC_DEV,
+	NL802154_CMD_DEL_SEC_DEV,
+	NL802154_CMD_GET_SEC_DEVKEY,		/* can dump */
+	NL802154_CMD_NEW_SEC_DEVKEY,
+	NL802154_CMD_DEL_SEC_DEVKEY,
+	NL802154_CMD_GET_SEC_LEVEL,		/* can dump */
+	NL802154_CMD_NEW_SEC_LEVEL,
+	NL802154_CMD_DEL_SEC_LEVEL,
+#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
+
+	/* used to define NL802154_CMD_MAX below */
+	__NL802154_CMD_AFTER_LAST,
+	NL802154_CMD_MAX = __NL802154_CMD_AFTER_LAST - 1
+};
+
+enum nl802154_attrs {
+/* don't change the order or add anything between, this is ABI! */
+/* currently we don't shipping this file via uapi, ignore the above one */
+	NL802154_ATTR_UNSPEC,
+
+	NL802154_ATTR_WPAN_PHY,
+	NL802154_ATTR_WPAN_PHY_NAME,
+
+	NL802154_ATTR_IFINDEX,
+	NL802154_ATTR_IFNAME,
+	NL802154_ATTR_IFTYPE,
+
+	NL802154_ATTR_WPAN_DEV,
+
+	NL802154_ATTR_PAGE,
+	NL802154_ATTR_CHANNEL,
+
+	NL802154_ATTR_PAN_ID,
+	NL802154_ATTR_SHORT_ADDR,
+
+	NL802154_ATTR_TX_POWER,
+
+	NL802154_ATTR_CCA_MODE,
+	NL802154_ATTR_CCA_OPT,
+	NL802154_ATTR_CCA_ED_LEVEL,
+
+	NL802154_ATTR_MAX_FRAME_RETRIES,
+
+	NL802154_ATTR_MAX_BE,
+	NL802154_ATTR_MIN_BE,
+	NL802154_ATTR_MAX_CSMA_BACKOFFS,
+
+	NL802154_ATTR_LBT_MODE,
+
+	NL802154_ATTR_GENERATION,
+
+	NL802154_ATTR_CHANNELS_SUPPORTED,
+	NL802154_ATTR_SUPPORTED_CHANNEL,
+
+	NL802154_ATTR_EXTENDED_ADDR,
+
+	NL802154_ATTR_WPAN_PHY_CAPS,
+
+	NL802154_ATTR_SUPPORTED_COMMANDS,
+
+	NL802154_ATTR_ACKREQ_DEFAULT,
+
+	NL802154_ATTR_PAD,
+
+	NL802154_ATTR_PID,
+	NL802154_ATTR_NETNS_FD,
+
+	/* add attributes here, update the policy in nl802154.c */
+
+#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
+	NL802154_ATTR_SEC_ENABLED,
+	NL802154_ATTR_SEC_OUT_LEVEL,
+	NL802154_ATTR_SEC_OUT_KEY_ID,
+	NL802154_ATTR_SEC_FRAME_COUNTER,
+
+	NL802154_ATTR_SEC_LEVEL,
+	NL802154_ATTR_SEC_DEVICE,
+	NL802154_ATTR_SEC_DEVKEY,
+	NL802154_ATTR_SEC_KEY,
+#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
+
+	__NL802154_ATTR_AFTER_LAST,
+	NL802154_ATTR_MAX = __NL802154_ATTR_AFTER_LAST - 1
+};
+
+enum nl802154_iftype {
+	/* for backwards compatibility TODO */
+	NL802154_IFTYPE_UNSPEC = -1,
+
+	NL802154_IFTYPE_NODE,
+	NL802154_IFTYPE_MONITOR,
+	NL802154_IFTYPE_COORD,
+
+	/* keep last */
+	NUM_NL802154_IFTYPES,
+	NL802154_IFTYPE_MAX = NUM_NL802154_IFTYPES - 1
+};
+
+/**
+ * enum nl802154_wpan_phy_capability_attr - wpan phy capability attributes
+ *
+ * @__NL802154_CAP_ATTR_INVALID: attribute number 0 is reserved
+ * @NL802154_CAP_ATTR_CHANNELS: a nested attribute for nl802154_channel_attr
+ * @NL802154_CAP_ATTR_TX_POWERS: a nested attribute for
+ *	nl802154_wpan_phy_tx_power
+ * @NL802154_CAP_ATTR_MIN_CCA_ED_LEVEL: minimum value for cca_ed_level
+ * @NL802154_CAP_ATTR_MAX_CCA_ED_LEVEL: maxmimum value for cca_ed_level
+ * @NL802154_CAP_ATTR_CCA_MODES: nl802154_cca_modes flags
+ * @NL802154_CAP_ATTR_CCA_OPTS: nl802154_cca_opts flags
+ * @NL802154_CAP_ATTR_MIN_MINBE: minimum of minbe value
+ * @NL802154_CAP_ATTR_MAX_MINBE: maximum of minbe value
+ * @NL802154_CAP_ATTR_MIN_MAXBE: minimum of maxbe value
+ * @NL802154_CAP_ATTR_MAX_MINBE: maximum of maxbe value
+ * @NL802154_CAP_ATTR_MIN_CSMA_BACKOFFS: minimum of csma backoff value
+ * @NL802154_CAP_ATTR_MAX_CSMA_BACKOFFS: maximum of csma backoffs value
+ * @NL802154_CAP_ATTR_MIN_FRAME_RETRIES: minimum of frame retries value
+ * @NL802154_CAP_ATTR_MAX_FRAME_RETRIES: maximum of frame retries value
+ * @NL802154_CAP_ATTR_IFTYPES: nl802154_iftype flags
+ * @NL802154_CAP_ATTR_LBT: nl802154_supported_bool_states flags
+ * @NL802154_CAP_ATTR_MAX: highest cap attribute currently defined
+ * @__NL802154_CAP_ATTR_AFTER_LAST: internal use
+ */
+enum nl802154_wpan_phy_capability_attr {
+	__NL802154_CAP_ATTR_INVALID,
+
+	NL802154_CAP_ATTR_IFTYPES,
+
+	NL802154_CAP_ATTR_CHANNELS,
+	NL802154_CAP_ATTR_TX_POWERS,
+
+	NL802154_CAP_ATTR_CCA_ED_LEVELS,
+	NL802154_CAP_ATTR_CCA_MODES,
+	NL802154_CAP_ATTR_CCA_OPTS,
+
+	NL802154_CAP_ATTR_MIN_MINBE,
+	NL802154_CAP_ATTR_MAX_MINBE,
+
+	NL802154_CAP_ATTR_MIN_MAXBE,
+	NL802154_CAP_ATTR_MAX_MAXBE,
+
+	NL802154_CAP_ATTR_MIN_CSMA_BACKOFFS,
+	NL802154_CAP_ATTR_MAX_CSMA_BACKOFFS,
+
+	NL802154_CAP_ATTR_MIN_FRAME_RETRIES,
+	NL802154_CAP_ATTR_MAX_FRAME_RETRIES,
+
+	NL802154_CAP_ATTR_LBT,
+
+	/* keep last */
+	__NL802154_CAP_ATTR_AFTER_LAST,
+	NL802154_CAP_ATTR_MAX = __NL802154_CAP_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum nl802154_cca_modes - cca modes
+ *
+ * @__NL802154_CCA_INVALID: cca mode number 0 is reserved
+ * @NL802154_CCA_ENERGY: Energy above threshold
+ * @NL802154_CCA_CARRIER: Carrier sense only
+ * @NL802154_CCA_ENERGY_CARRIER: Carrier sense with energy above threshold
+ * @NL802154_CCA_ALOHA: CCA shall always report an idle medium
+ * @NL802154_CCA_UWB_SHR: UWB preamble sense based on the SHR of a frame
+ * @NL802154_CCA_UWB_MULTIPLEXED: UWB preamble sense based on the packet with
+ *	the multiplexed preamble
+ * @__NL802154_CCA_ATTR_AFTER_LAST: Internal
+ * @NL802154_CCA_ATTR_MAX: Maximum CCA attribute number
+ */
+enum nl802154_cca_modes {
+	__NL802154_CCA_INVALID,
+	NL802154_CCA_ENERGY,
+	NL802154_CCA_CARRIER,
+	NL802154_CCA_ENERGY_CARRIER,
+	NL802154_CCA_ALOHA,
+	NL802154_CCA_UWB_SHR,
+	NL802154_CCA_UWB_MULTIPLEXED,
+
+	/* keep last */
+	__NL802154_CCA_ATTR_AFTER_LAST,
+	NL802154_CCA_ATTR_MAX = __NL802154_CCA_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum nl802154_cca_opts - additional options for cca modes
+ *
+ * @NL802154_CCA_OPT_ENERGY_CARRIER_OR: NL802154_CCA_ENERGY_CARRIER with OR
+ * @NL802154_CCA_OPT_ENERGY_CARRIER_AND: NL802154_CCA_ENERGY_CARRIER with AND
+ */
+enum nl802154_cca_opts {
+	NL802154_CCA_OPT_ENERGY_CARRIER_AND,
+	NL802154_CCA_OPT_ENERGY_CARRIER_OR,
+
+	/* keep last */
+	__NL802154_CCA_OPT_ATTR_AFTER_LAST,
+	NL802154_CCA_OPT_ATTR_MAX = __NL802154_CCA_OPT_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum nl802154_supported_bool_states - bool states for bool capability entry
+ *
+ * @NL802154_SUPPORTED_BOOL_FALSE: indicates to set false
+ * @NL802154_SUPPORTED_BOOL_TRUE: indicates to set true
+ * @__NL802154_SUPPORTED_BOOL_INVALD: reserved
+ * @NL802154_SUPPORTED_BOOL_BOTH: indicates to set true and false
+ * @__NL802154_SUPPORTED_BOOL_AFTER_LAST: Internal
+ * @NL802154_SUPPORTED_BOOL_MAX: highest value for bool states
+ */
+enum nl802154_supported_bool_states {
+	NL802154_SUPPORTED_BOOL_FALSE,
+	NL802154_SUPPORTED_BOOL_TRUE,
+	/* to handle them in a mask */
+	__NL802154_SUPPORTED_BOOL_INVALD,
+	NL802154_SUPPORTED_BOOL_BOTH,
+
+	/* keep last */
+	__NL802154_SUPPORTED_BOOL_AFTER_LAST,
+	NL802154_SUPPORTED_BOOL_MAX = __NL802154_SUPPORTED_BOOL_AFTER_LAST - 1
+};
+
+#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
+
+enum nl802154_dev_addr_modes {
+	NL802154_DEV_ADDR_NONE,
+	__NL802154_DEV_ADDR_INVALID,
+	NL802154_DEV_ADDR_SHORT,
+	NL802154_DEV_ADDR_EXTENDED,
+
+	/* keep last */
+	__NL802154_DEV_ADDR_AFTER_LAST,
+	NL802154_DEV_ADDR_MAX = __NL802154_DEV_ADDR_AFTER_LAST - 1
+};
+
+enum nl802154_dev_addr_attrs {
+	NL802154_DEV_ADDR_ATTR_UNSPEC,
+
+	NL802154_DEV_ADDR_ATTR_PAN_ID,
+	NL802154_DEV_ADDR_ATTR_MODE,
+	NL802154_DEV_ADDR_ATTR_SHORT,
+	NL802154_DEV_ADDR_ATTR_EXTENDED,
+	NL802154_DEV_ADDR_ATTR_PAD,
+
+	/* keep last */
+	__NL802154_DEV_ADDR_ATTR_AFTER_LAST,
+	NL802154_DEV_ADDR_ATTR_MAX = __NL802154_DEV_ADDR_ATTR_AFTER_LAST - 1
+};
+
+enum nl802154_key_id_modes {
+	NL802154_KEY_ID_MODE_IMPLICIT,
+	NL802154_KEY_ID_MODE_INDEX,
+	NL802154_KEY_ID_MODE_INDEX_SHORT,
+	NL802154_KEY_ID_MODE_INDEX_EXTENDED,
+
+	/* keep last */
+	__NL802154_KEY_ID_MODE_AFTER_LAST,
+	NL802154_KEY_ID_MODE_MAX = __NL802154_KEY_ID_MODE_AFTER_LAST - 1
+};
+
+enum nl802154_key_id_attrs {
+	NL802154_KEY_ID_ATTR_UNSPEC,
+
+	NL802154_KEY_ID_ATTR_MODE,
+	NL802154_KEY_ID_ATTR_INDEX,
+	NL802154_KEY_ID_ATTR_IMPLICIT,
+	NL802154_KEY_ID_ATTR_SOURCE_SHORT,
+	NL802154_KEY_ID_ATTR_SOURCE_EXTENDED,
+	NL802154_KEY_ID_ATTR_PAD,
+
+	/* keep last */
+	__NL802154_KEY_ID_ATTR_AFTER_LAST,
+	NL802154_KEY_ID_ATTR_MAX = __NL802154_KEY_ID_ATTR_AFTER_LAST - 1
+};
+
+enum nl802154_seclevels {
+	NL802154_SECLEVEL_NONE,
+	NL802154_SECLEVEL_MIC32,
+	NL802154_SECLEVEL_MIC64,
+	NL802154_SECLEVEL_MIC128,
+	NL802154_SECLEVEL_ENC,
+	NL802154_SECLEVEL_ENC_MIC32,
+	NL802154_SECLEVEL_ENC_MIC64,
+	NL802154_SECLEVEL_ENC_MIC128,
+
+	/* keep last */
+	__NL802154_SECLEVEL_AFTER_LAST,
+	NL802154_SECLEVEL_MAX = __NL802154_SECLEVEL_AFTER_LAST - 1
+};
+
+enum nl802154_frames {
+	NL802154_FRAME_BEACON,
+	NL802154_FRAME_DATA,
+	NL802154_FRAME_ACK,
+	NL802154_FRAME_CMD,
+
+	/* keep last */
+	__NL802154_FRAME_AFTER_LAST,
+	NL802154_FRAME_MAX = __NL802154_FRAME_AFTER_LAST - 1
+};
+
+enum nl802154_cmd_frames {
+	__NL802154_CMD_FRAME_INVALID,
+	NL802154_CMD_FRAME_ASSOC_REQUEST,
+	NL802154_CMD_FRAME_ASSOC_RESPONSE,
+	NL802154_CMD_FRAME_DISASSOC_NOTIFY,
+	NL802154_CMD_FRAME_DATA_REQUEST,
+	NL802154_CMD_FRAME_PAN_ID_CONFLICT_NOTIFY,
+	NL802154_CMD_FRAME_ORPHAN_NOTIFY,
+	NL802154_CMD_FRAME_BEACON_REQUEST,
+	NL802154_CMD_FRAME_COORD_REALIGNMENT,
+	NL802154_CMD_FRAME_GTS_REQUEST,
+
+	/* keep last */
+	__NL802154_CMD_FRAME_AFTER_LAST,
+	NL802154_CMD_FRAME_MAX = __NL802154_CMD_FRAME_AFTER_LAST - 1
+};
+
+enum nl802154_seclevel_attrs {
+	NL802154_SECLEVEL_ATTR_UNSPEC,
+
+	NL802154_SECLEVEL_ATTR_LEVELS,
+	NL802154_SECLEVEL_ATTR_FRAME,
+	NL802154_SECLEVEL_ATTR_CMD_FRAME,
+	NL802154_SECLEVEL_ATTR_DEV_OVERRIDE,
+
+	/* keep last */
+	__NL802154_SECLEVEL_ATTR_AFTER_LAST,
+	NL802154_SECLEVEL_ATTR_MAX = __NL802154_SECLEVEL_ATTR_AFTER_LAST - 1
+};
+
+/* TODO what is this? couldn't find in mib */
+enum {
+	NL802154_DEVKEY_IGNORE,
+	NL802154_DEVKEY_RESTRICT,
+	NL802154_DEVKEY_RECORD,
+
+	/* keep last */
+	__NL802154_DEVKEY_AFTER_LAST,
+	NL802154_DEVKEY_MAX = __NL802154_DEVKEY_AFTER_LAST - 1
+};
+
+enum nl802154_dev {
+	NL802154_DEV_ATTR_UNSPEC,
+
+	NL802154_DEV_ATTR_FRAME_COUNTER,
+	NL802154_DEV_ATTR_PAN_ID,
+	NL802154_DEV_ATTR_SHORT_ADDR,
+	NL802154_DEV_ATTR_EXTENDED_ADDR,
+	NL802154_DEV_ATTR_SECLEVEL_EXEMPT,
+	NL802154_DEV_ATTR_KEY_MODE,
+	NL802154_DEV_ATTR_PAD,
+
+	/* keep last */
+	__NL802154_DEV_ATTR_AFTER_LAST,
+	NL802154_DEV_ATTR_MAX = __NL802154_DEV_ATTR_AFTER_LAST - 1
+};
+
+enum nl802154_devkey {
+	NL802154_DEVKEY_ATTR_UNSPEC,
+
+	NL802154_DEVKEY_ATTR_FRAME_COUNTER,
+	NL802154_DEVKEY_ATTR_EXTENDED_ADDR,
+	NL802154_DEVKEY_ATTR_ID,
+	NL802154_DEVKEY_ATTR_PAD,
+
+	/* keep last */
+	__NL802154_DEVKEY_ATTR_AFTER_LAST,
+	NL802154_DEVKEY_ATTR_MAX = __NL802154_DEVKEY_ATTR_AFTER_LAST - 1
+};
+
+enum nl802154_key {
+	NL802154_KEY_ATTR_UNSPEC,
+
+	NL802154_KEY_ATTR_ID,
+	NL802154_KEY_ATTR_USAGE_FRAMES,
+	NL802154_KEY_ATTR_USAGE_CMDS,
+	NL802154_KEY_ATTR_BYTES,
+
+	/* keep last */
+	__NL802154_KEY_ATTR_AFTER_LAST,
+	NL802154_KEY_ATTR_MAX = __NL802154_KEY_ATTR_AFTER_LAST - 1
+};
+
+#define NL802154_KEY_SIZE		16
+#define NL802154_CMD_FRAME_NR_IDS	256
+
+#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
+
+#endif /* __NL802154_H */
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index f8f4627e..82f9e2fb 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -602,12 +602,6 @@ link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
-static gboolean
-link_set_sriov_num_vfs (NMPlatform *platform, int ifindex, guint num_vfs)
-{
-	return TRUE;
-}
-
 static const char *
 link_get_udi (NMPlatform *platform, int ifindex)
 {
@@ -893,12 +887,6 @@ wifi_get_bssid (NMPlatform *platform, int ifindex, guint8 *bssid)
 	return FALSE;
 }
 
-static GByteArray *
-wifi_get_ssid (NMPlatform *platform, int ifindex)
-{
-	return NULL;
-}
-
 static guint32
 wifi_get_frequency (NMPlatform *platform, int ifindex)
 {
@@ -1422,7 +1410,6 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 
 	platform_class->link_set_address = link_set_address;
 	platform_class->link_set_mtu = link_set_mtu;
-	platform_class->link_set_sriov_num_vfs = link_set_sriov_num_vfs;
 
 	platform_class->link_get_driver_info = link_get_driver_info;
 
@@ -1442,7 +1429,6 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 
 	platform_class->wifi_get_capabilities = wifi_get_capabilities;
 	platform_class->wifi_get_bssid = wifi_get_bssid;
-	platform_class->wifi_get_ssid = wifi_get_ssid;
 	platform_class->wifi_get_frequency = wifi_get_frequency;
 	platform_class->wifi_get_quality = wifi_get_quality;
 	platform_class->wifi_get_rate = wifi_get_rate;
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 9bd662a1..e73d5d8c 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2012 - 2017 Red Hat, Inc.
+ * Copyright (C) 2012 - 2018 Red Hat, Inc.
  */
 #include "nm-default.h"
 
@@ -44,15 +44,18 @@
 #include "nm-core-internal.h"
 #include "nm-setting-vlan.h"
 
+#include "nm-utils/nm-secret-utils.h"
 #include "nm-netlink.h"
 #include "nm-core-utils.h"
 #include "nmp-object.h"
 #include "nmp-netns.h"
 #include "nm-platform-utils.h"
 #include "nm-platform-private.h"
-#include "wifi/wifi-utils.h"
-#include "wifi/wifi-utils-wext.h"
+#include "wifi/nm-wifi-utils.h"
+#include "wifi/nm-wifi-utils-wext.h"
+#include "wpan/nm-wpan-utils.h"
 #include "nm-utils/unaligned.h"
+#include "nm-utils/nm-io-utils.h"
 #include "nm-utils/nm-udev-utils.h"
 
 /*****************************************************************************/
@@ -154,6 +157,13 @@ G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1));
 
 /*****************************************************************************/
 
+/* Appeared in in kernel prior to 3.13 dated 19 January, 2014 */
+#ifndef ARPHRD_6LOWPAN
+#define ARPHRD_6LOWPAN 825
+#endif
+
+/*****************************************************************************/
+
 #define IFLA_MACSEC_UNSPEC              0
 #define IFLA_MACSEC_SCI                 1
 #define IFLA_MACSEC_PORT                2
@@ -173,63 +183,79 @@ G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1));
 
 /*****************************************************************************/
 
-#define _NMLOG_PREFIX_NAME                "platform-linux"
-#define _NMLOG_DOMAIN                     LOGD_PLATFORM
-#define _NMLOG2_DOMAIN                    LOGD_PLATFORM
-#define _NMLOG(level, ...)                _LOG     (       level, _NMLOG_DOMAIN,  platform, __VA_ARGS__)
-#define _NMLOG_err(errsv, level, ...)     _LOG_err (errsv, level, _NMLOG_DOMAIN,  platform, __VA_ARGS__)
-#define _NMLOG2(level, ...)               _LOG     (       level, _NMLOG2_DOMAIN, NULL,     __VA_ARGS__)
-#define _NMLOG2_err(errsv, level, ...)    _LOG_err (errsv, level, _NMLOG2_DOMAIN, NULL,     __VA_ARGS__)
+#define WG_CMD_GET_DEVICE 0
+#define WG_CMD_SET_DEVICE 1
+
+#define WGDEVICE_A_UNSPEC                      0
+#define WGDEVICE_A_IFINDEX                     1
+#define WGDEVICE_A_IFNAME                      2
+#define WGDEVICE_A_PRIVATE_KEY                 3
+#define WGDEVICE_A_PUBLIC_KEY                  4
+#define WGDEVICE_A_FLAGS                       5
+#define WGDEVICE_A_LISTEN_PORT                 6
+#define WGDEVICE_A_FWMARK                      7
+#define WGDEVICE_A_PEERS                       8
+#define WGDEVICE_A_MAX                         8
+
+#define WGPEER_A_UNSPEC                        0
+#define WGPEER_A_PUBLIC_KEY                    1
+#define WGPEER_A_PRESHARED_KEY                 2
+#define WGPEER_A_FLAGS                         3
+#define WGPEER_A_ENDPOINT                      4
+#define WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL 5
+#define WGPEER_A_LAST_HANDSHAKE_TIME           6
+#define WGPEER_A_RX_BYTES                      7
+#define WGPEER_A_TX_BYTES                      8
+#define WGPEER_A_ALLOWEDIPS                    9
+#define WGPEER_A_MAX                           9
+
+#define WGALLOWEDIP_A_UNSPEC                   0
+#define WGALLOWEDIP_A_FAMILY                   1
+#define WGALLOWEDIP_A_IPADDR                   2
+#define WGALLOWEDIP_A_CIDR_MASK                3
+#define WGALLOWEDIP_A_MAX                      3
 
-#define _LOG_print(__level, __domain, __errsv, self, ...) \
-    G_STMT_START { \
-        char __prefix[32]; \
-        const char *__p_prefix = _NMLOG_PREFIX_NAME; \
-        NMPlatform *const __self = (self); \
-        \
-        if (__self && nm_platform_get_log_with_ptr (__self)) { \
-            g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
-            __p_prefix = __prefix; \
-        } \
-        _nm_log (__level, __domain, __errsv, NULL, NULL, \
-                 "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                 __p_prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
-    } G_STMT_END
+/*****************************************************************************/
 
-#define _LOG(level, domain, self, ...) \
-    G_STMT_START { \
-        const NMLogLevel __level = (level); \
-        const NMLogDomain __domain = (domain); \
-        \
-        if (nm_logging_enabled (__level, __domain)) { \
-            _LOG_print (__level, __domain, 0, self, __VA_ARGS__); \
-        } \
-    } G_STMT_END
+/* Redefine VF enums and structures that are not available on older kernels. */
+
+#define IFLA_VF_UNSPEC                 0
+#define IFLA_VF_MAC                    1
+#define IFLA_VF_VLAN                   2
+#define IFLA_VF_TX_RATE                3
+#define IFLA_VF_SPOOFCHK               4
+#define IFLA_VF_LINK_STATE             5
+#define IFLA_VF_RATE                   6
+#define IFLA_VF_RSS_QUERY_EN           7
+#define IFLA_VF_STATS                  8
+#define IFLA_VF_TRUST                  9
+#define IFLA_VF_IB_NODE_GUID           10
+#define IFLA_VF_IB_PORT_GUID           11
+#define IFLA_VF_VLAN_LIST              12
+
+#define IFLA_VF_VLAN_INFO_UNSPEC       0
+#define IFLA_VF_VLAN_INFO              1
+
+/* valid for TRUST, SPOOFCHK, LINK_STATE, RSS_QUERY_EN */
+struct _ifla_vf_setting {
+	guint32 vf;
+	guint32 setting;
+};
 
-#define _LOG_err(errsv, level, domain, self, ...) \
-    G_STMT_START { \
-        const NMLogLevel __level = (level); \
-        const NMLogDomain __domain = (domain); \
-        \
-        if (nm_logging_enabled (__level, __domain)) { \
-            int __errsv = (errsv); \
-            \
-            /* The %m format specifier (GNU extension) would alread allow you to specify the error
-             * message conveniently (and nm_log would get that right too). But we don't want to depend
-             * on that, so instead append the message at the end.
-             * Currently users are expected not to use %m in the format string. */ \
-            _LOG_print (__level, __domain, __errsv, self, \
-                        _NM_UTILS_MACRO_FIRST (__VA_ARGS__) ": %s (%d)" \
-                        _NM_UTILS_MACRO_REST (__VA_ARGS__), \
-                        g_strerror (__errsv), __errsv); \
-        } \
-    } G_STMT_END
+struct _ifla_vf_rate {
+	guint32 vf;
+	guint32 min_tx_rate;
+	guint32 max_tx_rate;
+};
 
-#define LOG_FMT_IP_TUNNEL "adding %s '%s' parent %u local %s remote %s"
+struct _ifla_vf_vlan_info {
+	guint32 vf;
+	guint32 vlan; /* 0 - 4095, 0 disables VLAN filter */
+	guint32 qos;
+	guint16 vlan_proto; /* VLAN protocol, either 802.1Q or 802.1ad */
+};
 
-/******************************************************************
- * Forward declarations and enums
- ******************************************************************/
+/*****************************************************************************/
 
 typedef enum {
 	INFINIBAND_ACTION_CREATE_CHILD,
@@ -307,6 +333,133 @@ typedef enum {
 	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS,
 } WaitForNlResponseResult;
 
+typedef enum {
+	DELAYED_ACTION_RESPONSE_TYPE_VOID                       = 0,
+	DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS    = 1,
+	DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET                  = 2,
+} DelayedActionWaitForNlResponseType;
+
+typedef struct {
+	guint32 seq_number;
+	WaitForNlResponseResult seq_result;
+	DelayedActionWaitForNlResponseType response_type;
+	gint64 timeout_abs_ns;
+	WaitForNlResponseResult *out_seq_result;
+	char **out_errmsg;
+	union {
+		int *out_refresh_all_in_progress;
+		NMPObject **out_route_get;
+		gpointer out_data;
+	} response;
+} DelayedActionWaitForNlResponseData;
+
+/*****************************************************************************/
+
+typedef struct {
+	struct nl_sock *genl;
+
+	struct nl_sock *nlh;
+	guint32 nlh_seq_next;
+#if NM_MORE_LOGGING
+	guint32 nlh_seq_last_handled;
+#endif
+	guint32 nlh_seq_last_seen;
+	GIOChannel *event_channel;
+	guint event_id;
+
+	bool pruning[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
+
+	bool sysctl_get_warned;
+	GHashTable *sysctl_get_prev_values;
+
+	NMUdevClient *udev_client;
+
+	struct {
+		/* which delayed actions are scheduled, as marked in @flags.
+		 * Some types have additional arguments in the fields below. */
+		DelayedActionType flags;
+
+		/* counter that a refresh all action is in progress, separated
+		 * by type. */
+		int refresh_all_in_progress[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
+
+		GPtrArray *list_master_connected;
+		GPtrArray *list_refresh_link;
+		GArray *list_wait_for_nl_response;
+
+		int is_handling;
+	} delayed_action;
+} NMLinuxPlatformPrivate;
+
+struct _NMLinuxPlatform {
+	NMPlatform parent;
+	NMLinuxPlatformPrivate _priv;
+};
+
+struct _NMLinuxPlatformClass {
+	NMPlatformClass parent;
+};
+
+G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
+
+#define NM_LINUX_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMLinuxPlatform, NM_IS_LINUX_PLATFORM, NMPlatform)
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME                "platform-linux"
+#define _NMLOG_DOMAIN                     LOGD_PLATFORM
+#define _NMLOG2_DOMAIN                    LOGD_PLATFORM
+#define _NMLOG(level, ...)                _LOG     (       level, _NMLOG_DOMAIN,  platform, __VA_ARGS__)
+#define _NMLOG_err(errsv, level, ...)     _LOG_err (errsv, level, _NMLOG_DOMAIN,  platform, __VA_ARGS__)
+#define _NMLOG2(level, ...)               _LOG     (       level, _NMLOG2_DOMAIN, NULL,     __VA_ARGS__)
+#define _NMLOG2_err(errsv, level, ...)    _LOG_err (errsv, level, _NMLOG2_DOMAIN, NULL,     __VA_ARGS__)
+
+#define _LOG_print(__level, __domain, __errsv, self, ...) \
+    G_STMT_START { \
+        char __prefix[32]; \
+        const char *__p_prefix = _NMLOG_PREFIX_NAME; \
+        NMPlatform *const __self = (self); \
+        \
+        if (__self && nm_platform_get_log_with_ptr (__self)) { \
+            g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
+            __p_prefix = __prefix; \
+        } \
+        _nm_log (__level, __domain, __errsv, NULL, NULL, \
+                 "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                 __p_prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+    } G_STMT_END
+
+#define _LOG(level, domain, self, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        const NMLogDomain __domain = (domain); \
+        \
+        if (nm_logging_enabled (__level, __domain)) { \
+            _LOG_print (__level, __domain, 0, self, __VA_ARGS__); \
+        } \
+    } G_STMT_END
+
+#define _LOG_err(errsv, level, domain, self, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        const NMLogDomain __domain = (domain); \
+        \
+        if (nm_logging_enabled (__level, __domain)) { \
+            int __errsv = (errsv); \
+            \
+            /* The %m format specifier (GNU extension) would alread allow you to specify the error
+             * message conveniently (and nm_log would get that right too). But we don't want to depend
+             * on that, so instead append the message at the end.
+             * Currently users are expected not to use %m in the format string. */ \
+            _LOG_print (__level, __domain, __errsv, self, \
+                        _NM_UTILS_MACRO_FIRST (__VA_ARGS__) ": %s (%d)" \
+                        _NM_UTILS_MACRO_REST (__VA_ARGS__), \
+                        g_strerror (__errsv), __errsv); \
+        } \
+    } G_STMT_END
+
+/*****************************************************************************/
+
 static void delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gpointer user_data);
 static gboolean delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink);
 static void do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const char *name);
@@ -317,6 +470,7 @@ static void cache_on_change (NMPlatform *platform,
                              const NMPObject *obj_new);
 static void cache_prune_all (NMPlatform *platform);
 static gboolean event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks);
+static struct nl_sock *_genl_sock (NMLinuxPlatform *platform);
 
 /*****************************************************************************/
 
@@ -533,6 +687,8 @@ static const LinkDesc linktypes[] = {
 	{ NM_LINK_TYPE_WIFI,          "wifi",        NULL,          "wlan" },
 	{ NM_LINK_TYPE_WWAN_NET,      "wwan",        NULL,          "wwan" },
 	{ NM_LINK_TYPE_WIMAX,         "wimax",       "wimax",       "wimax" },
+	{ NM_LINK_TYPE_WPAN,          "wpan",        NULL,          NULL },
+	{ NM_LINK_TYPE_6LOWPAN,       "6lowpan",     NULL,          NULL },
 
 	{ NM_LINK_TYPE_BNEP,          "bluetooth",   NULL,          "bluetooth" },
 	{ NM_LINK_TYPE_DUMMY,         "dummy",       "dummy",       NULL },
@@ -540,6 +696,8 @@ static const LinkDesc linktypes[] = {
 	{ NM_LINK_TYPE_GRETAP,        "gretap",      "gretap",      NULL },
 	{ NM_LINK_TYPE_IFB,           "ifb",         "ifb",         NULL },
 	{ NM_LINK_TYPE_IP6TNL,        "ip6tnl",      "ip6tnl",      NULL },
+	{ NM_LINK_TYPE_IP6GRE,        "ip6gre",      "ip6gre",      NULL },
+	{ NM_LINK_TYPE_IP6GRETAP,     "ip6gretap",   "ip6gretap",   NULL },
 	{ NM_LINK_TYPE_IPIP,          "ipip",        "ipip",        NULL },
 	{ NM_LINK_TYPE_LOOPBACK,      "loopback",    NULL,          NULL },
 	{ NM_LINK_TYPE_MACSEC,        "macsec",      "macsec",      NULL },
@@ -552,6 +710,7 @@ static const LinkDesc linktypes[] = {
 	{ NM_LINK_TYPE_VETH,          "veth",        "veth",        NULL },
 	{ NM_LINK_TYPE_VLAN,          "vlan",        "vlan",        "vlan" },
 	{ NM_LINK_TYPE_VXLAN,         "vxlan",       "vxlan",       "vxlan" },
+	{ NM_LINK_TYPE_WIREGUARD,     "wireguard",   "wireguard",   "wireguard" },
 
 	{ NM_LINK_TYPE_BRIDGE,        "bridge",      "bridge",      "bridge" },
 	{ NM_LINK_TYPE_BOND,          "bond",        "bond",        "bond" },
@@ -725,19 +884,21 @@ _addrtime_get_lifetimes (guint32 timestamp,
 /*****************************************************************************/
 
 static const NMPObject *
-_lookup_cached_link (const NMPCache *cache, int ifindex, gboolean *completed_from_cache, const NMPObject **link_cached)
+_lookup_cached_link (const NMPCache *cache,
+                     int ifindex,
+                     gboolean *completed_from_cache,
+                     const NMPObject **link_cached)
 {
 	const NMPObject *obj;
 
 	nm_assert (completed_from_cache && link_cached);
 
 	if (!*completed_from_cache) {
-		obj = ifindex > 0 && cache ? nmp_cache_lookup_link (cache, ifindex) : NULL;
+		obj = ifindex > 0 && cache
+		      ? nmp_cache_lookup_link (cache, ifindex)
+		      : NULL;
 
-		if (obj && obj->_link.netlink.is_in_netlink)
-			*link_cached = obj;
-		else
-			*link_cached = NULL;
+		*link_cached = obj;
 		*completed_from_cache = TRUE;
 	}
 	return *link_cached;
@@ -755,7 +916,9 @@ _linktype_read_devtype (int dirfd)
 
 	nm_assert (dirfd >= 0);
 
-	if (nm_utils_file_get_contents (dirfd, "uevent", 1*1024*1024, &contents, NULL, NULL) < 0)
+	if (nm_utils_file_get_contents (dirfd, "uevent", 1*1024*1024,
+	                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+	                                &contents, NULL, NULL) < 0)
 		return NULL;
 	for (cont = contents; cont; cont = end) {
 		end = strpbrk (cont, "\r\n");
@@ -806,6 +969,7 @@ _linktype_get_type (NMPlatform *platform,
 		 * when moving interfce to other netns). Thus here there is a tiny potential
 		 * of messing stuff up. */
 		if (   obj
+		    && obj->_link.netlink.is_in_netlink
 		    && !NM_IN_SET (obj->link.type, NM_LINK_TYPE_UNKNOWN, NM_LINK_TYPE_NONE)
 		    && nm_streq (ifname, obj->link.name)
 		    && (   !kind
@@ -841,6 +1005,10 @@ _linktype_get_type (NMPlatform *platform,
 		return NM_LINK_TYPE_IP6TNL;
 	else if (arptype == ARPHRD_PPP)
 		return NM_LINK_TYPE_PPP;
+	else if (arptype == ARPHRD_IEEE802154)
+		return NM_LINK_TYPE_WPAN;
+	else if (arptype == ARPHRD_6LOWPAN)
+		return NM_LINK_TYPE_6LOWPAN;
 
 	{
 		NMPUtilsEthtoolDriverInfo driver_info;
@@ -885,7 +1053,7 @@ _linktype_get_type (NMPlatform *platform,
 			}
 
 			/* Fallback for drivers that don't call SET_NETDEV_DEVTYPE() */
-			if (wifi_utils_is_wifi (dirfd, ifname_verified))
+			if (nm_wifi_utils_is_wifi (dirfd, ifname_verified))
 				return NM_LINK_TYPE_WIFI;
 		}
 
@@ -953,18 +1121,21 @@ _nl_addattr_l (struct nlmsghdr *n,
  * NMPObject/netlink functions
  ******************************************************************/
 
-#define _check_addr_or_errout(tb, attr, addr_len) \
+#define _check_addr_or_return_val(tb, attr, addr_len, ret_val) \
 	({ \
 	    const struct nlattr *__t = (tb)[(attr)]; \
 		\
 	    if (__t) { \
 			if (nla_len (__t) != (addr_len)) { \
-				goto errout; \
+				return ret_val; \
 			} \
 		} \
 		!!__t; \
 	})
 
+#define _check_addr_or_return_null(tb, attr, addr_len) \
+	_check_addr_or_return_val (tb, attr, addr_len, NULL)
+
 /*****************************************************************************/
 
 /* Copied and heavily modified from libnl3's inet6_parse_protinfo(). */
@@ -991,20 +1162,19 @@ _parse_af_inet6 (NMPlatform *platform,
 	gboolean token_valid = FALSE;
 	gboolean addr_gen_mode_valid = FALSE;
 	guint8 i6_addr_gen_mode_inv = 0;
-	gboolean success = FALSE;
 
 	err = nla_parse_nested (tb, IFLA_INET6_MAX, attr, policy);
 	if (err < 0)
-		goto errout;
+		return FALSE;
 
 	if (tb[IFLA_INET6_CONF] && nla_len(tb[IFLA_INET6_CONF]) % 4)
-		goto errout;
+		return FALSE;
 	if (tb[IFLA_INET6_STATS] && nla_len(tb[IFLA_INET6_STATS]) % 8)
-		goto errout;
+		return FALSE;
 	if (tb[IFLA_INET6_ICMP6STATS] && nla_len(tb[IFLA_INET6_ICMP6STATS]) % 8)
-		goto errout;
+		return FALSE;
 
-	if (_check_addr_or_errout (tb, IFLA_INET6_TOKEN, sizeof (struct in6_addr))) {
+	if (_check_addr_or_return_val (tb, IFLA_INET6_TOKEN, sizeof (struct in6_addr), FALSE)) {
 		nla_memcpy (&i6_token, tb[IFLA_INET6_TOKEN], sizeof (struct in6_addr));
 		token_valid = TRUE;
 	}
@@ -1020,12 +1190,11 @@ _parse_af_inet6 (NMPlatform *platform,
 		if (i6_addr_gen_mode_inv == 0) {
 			/* an inverse addrgenmode of zero is unexpected. We need to reserve zero
 			 * to signal "unset". */
-			goto errout;
+			return FALSE;
 		}
 		addr_gen_mode_valid = TRUE;
 	}
 
-	success = TRUE;
 	if (token_valid) {
 		*out_token_valid = token_valid;
 		nm_utils_ipv6_interface_identifier_get_from_addr (out_token, &i6_token);
@@ -1034,8 +1203,7 @@ _parse_af_inet6 (NMPlatform *platform,
 		*out_addr_gen_mode_valid = addr_gen_mode_valid;
 		*out_addr_gen_mode_inv = i6_addr_gen_mode_inv;
 	}
-errout:
-	return success;
+	return TRUE;
 }
 
 /*****************************************************************************/
@@ -1059,15 +1227,23 @@ _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 	int err;
 	NMPObject *obj;
 	NMPlatformLnkGre *props;
+	gboolean is_tap;
+
+	if (!info_data || !kind)
+		return NULL;
 
-	if (!info_data || g_strcmp0 (kind, "gre"))
+	if (nm_streq (kind, "gretap"))
+		is_tap = TRUE;
+	else if (nm_streq (kind, "gre"))
+		is_tap = FALSE;
+	else
 		return NULL;
 
 	err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data, policy);
 	if (err < 0)
 		return NULL;
 
-	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_GRE, NULL);
+	obj = nmp_object_new (is_tap ? NMP_OBJECT_TYPE_LNK_GRETAP : NMP_OBJECT_TYPE_LNK_GRE, NULL);
 	props = &obj->lnk_gre;
 
 	props->parent_ifindex = tb[IFLA_GRE_LINK] ? nla_get_u32 (tb[IFLA_GRE_LINK]) : 0;
@@ -1080,6 +1256,7 @@ _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 	props->tos = tb[IFLA_GRE_TOS] ? nla_get_u8 (tb[IFLA_GRE_TOS]) : 0;
 	props->ttl = tb[IFLA_GRE_TTL] ? nla_get_u8 (tb[IFLA_GRE_TTL]) : 0;
 	props->path_mtu_discovery = !tb[IFLA_GRE_PMTUDISC] || !!nla_get_u8 (tb[IFLA_GRE_PMTUDISC]);
+	props->is_tap = is_tap;
 
 	return obj;
 }
@@ -1200,6 +1377,79 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 	return obj;
 }
 
+static NMPObject *
+_parse_lnk_ip6gre (const char *kind, struct nlattr *info_data)
+{
+	static const struct nla_policy policy[IFLA_GRE_MAX + 1] = {
+		[IFLA_GRE_LINK]        = { .type = NLA_U32 },
+		[IFLA_GRE_IFLAGS]      = { .type = NLA_U16 },
+		[IFLA_GRE_OFLAGS]      = { .type = NLA_U16 },
+		[IFLA_GRE_IKEY]        = { .type = NLA_U32 },
+		[IFLA_GRE_OKEY]        = { .type = NLA_U32 },
+		[IFLA_GRE_LOCAL]       = { .type = NLA_UNSPEC,
+		                             .minlen = sizeof (struct in6_addr)},
+		[IFLA_GRE_REMOTE]      = { .type = NLA_UNSPEC,
+		                             .minlen = sizeof (struct in6_addr)},
+		[IFLA_GRE_TTL]         = { .type = NLA_U8 },
+		[IFLA_GRE_ENCAP_LIMIT] = { .type = NLA_U8 },
+		[IFLA_GRE_FLOWINFO]    = { .type = NLA_U32 },
+		[IFLA_GRE_FLAGS]       = { .type = NLA_U32 },
+	};
+	struct nlattr *tb[IFLA_GRE_MAX + 1];
+	int err;
+	NMPObject *obj;
+	NMPlatformLnkIp6Tnl *props;
+	guint32 flowinfo;
+	gboolean is_tap;
+
+	if (!info_data || !kind)
+		return NULL;
+
+	if (nm_streq (kind, "ip6gre"))
+		is_tap = FALSE;
+	else if (nm_streq (kind, "ip6gretap"))
+		is_tap = TRUE;
+	else
+		return NULL;
+
+	err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	obj = nmp_object_new (is_tap ? NMP_OBJECT_TYPE_LNK_IP6GRETAP : NMP_OBJECT_TYPE_LNK_IP6GRE, NULL);
+	props = &obj->lnk_ip6tnl;
+	props->is_gre = TRUE;
+	props->is_tap = is_tap;
+
+	if (tb[IFLA_GRE_LINK])
+		props->parent_ifindex = nla_get_u32 (tb[IFLA_GRE_LINK]);
+	if (tb[IFLA_GRE_IFLAGS])
+		props->input_flags = ntohs (nla_get_u16 (tb[IFLA_GRE_IFLAGS]));
+	if (tb[IFLA_GRE_OFLAGS])
+		props->output_flags = ntohs (nla_get_u16 (tb[IFLA_GRE_OFLAGS]));
+	if (tb[IFLA_GRE_IKEY])
+		props->input_key = ntohl (nla_get_u32 (tb[IFLA_GRE_IKEY]));
+	if (tb[IFLA_GRE_OKEY])
+		props->output_key = ntohl (nla_get_u32 (tb[IFLA_GRE_OKEY]));
+	if (tb[IFLA_GRE_LOCAL])
+		memcpy (&props->local, nla_data (tb[IFLA_GRE_LOCAL]), sizeof (props->local));
+	if (tb[IFLA_GRE_REMOTE])
+		memcpy (&props->remote, nla_data (tb[IFLA_GRE_REMOTE]), sizeof (props->remote));
+	if (tb[IFLA_GRE_TTL])
+		props->ttl = nla_get_u8 (tb[IFLA_GRE_TTL]);
+	if (tb[IFLA_GRE_ENCAP_LIMIT])
+		props->encap_limit = nla_get_u8 (tb[IFLA_GRE_ENCAP_LIMIT]);
+	if (tb[IFLA_GRE_FLOWINFO]) {
+		flowinfo = ntohl (nla_get_u32 (tb[IFLA_GRE_FLOWINFO]));
+		props->flow_label = flowinfo & IP6_FLOWINFO_FLOWLABEL_MASK;
+		props->tclass = (flowinfo & IP6_FLOWINFO_TCLASS_MASK) >> IP6_FLOWINFO_TCLASS_SHIFT;
+	}
+	if (tb[IFLA_GRE_FLAGS])
+		props->flags = nla_get_u32 (tb[IFLA_GRE_FLAGS]);
+
+	return obj;
+}
+
 /*****************************************************************************/
 
 static NMPObject *
@@ -1663,6 +1913,378 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 
 /*****************************************************************************/
 
+static gboolean
+_wireguard_update_from_allowed_ips_nla (NMPWireGuardAllowedIP *allowed_ip,
+                                        struct nlattr *nlattr)
+{
+	static const struct nla_policy policy[WGALLOWEDIP_A_MAX + 1] = {
+		[WGALLOWEDIP_A_FAMILY]    = { .type = NLA_U16 },
+		[WGALLOWEDIP_A_IPADDR]    = { .minlen = sizeof (struct in_addr) },
+		[WGALLOWEDIP_A_CIDR_MASK] = { .type = NLA_U8 },
+	};
+	struct nlattr *tb[WGALLOWEDIP_A_MAX + 1];
+	int family;
+	int addr_len;
+
+	if (nla_parse_nested (tb, WGALLOWEDIP_A_MAX, nlattr, policy) < 0)
+		return FALSE;
+
+	if (!tb[WGALLOWEDIP_A_FAMILY])
+		return FALSE;
+
+	family = nla_get_u16 (tb[WGALLOWEDIP_A_FAMILY]);
+	if (family == AF_INET)
+		addr_len = sizeof (in_addr_t);
+	else if (family == AF_INET6)
+		addr_len = sizeof (struct in6_addr);
+	else
+		return FALSE;
+
+	_check_addr_or_return_val (tb, WGALLOWEDIP_A_IPADDR, addr_len, FALSE);
+
+	memset (allowed_ip, 0, sizeof (NMPWireGuardAllowedIP));
+
+	allowed_ip->family = family;
+	nm_assert ((int) allowed_ip->family == family);
+
+	if (tb[WGALLOWEDIP_A_IPADDR])
+		nla_memcpy (&allowed_ip->addr, tb[WGALLOWEDIP_A_IPADDR], addr_len);
+	if (tb[WGALLOWEDIP_A_CIDR_MASK])
+		allowed_ip->mask = nla_get_u8 (tb[WGALLOWEDIP_A_CIDR_MASK]);
+
+	return TRUE;
+}
+
+typedef struct {
+	CList lst;
+	NMPWireGuardPeer data;
+} WireGuardPeerConstruct;
+
+static gboolean
+_wireguard_update_from_peers_nla (CList *peers,
+                                  GArray **p_allowed_ips,
+                                  struct nlattr *peer_attr)
+{
+	static const struct nla_policy policy[WGPEER_A_MAX + 1] = {
+		[WGPEER_A_PUBLIC_KEY]                    = { .minlen = NMP_WIREGUARD_PUBLIC_KEY_LEN },
+		[WGPEER_A_PRESHARED_KEY]                 = { },
+		[WGPEER_A_FLAGS]                         = { .type = NLA_U32 },
+		[WGPEER_A_ENDPOINT]                      = { },
+		[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL] = { .type = NLA_U16 },
+		[WGPEER_A_LAST_HANDSHAKE_TIME]           = { },
+		[WGPEER_A_RX_BYTES]                      = { .type = NLA_U64 },
+		[WGPEER_A_TX_BYTES]                      = { .type = NLA_U64 },
+		[WGPEER_A_ALLOWEDIPS]                    = { .type = NLA_NESTED },
+	};
+	WireGuardPeerConstruct *peer_c;
+	struct nlattr *tb[WGPEER_A_MAX + 1];
+
+	if (nla_parse_nested (tb, WGPEER_A_MAX, peer_attr, policy) < 0)
+		return FALSE;
+
+	if (!tb[WGPEER_A_PUBLIC_KEY])
+		return FALSE;
+
+	/* a peer with the same public key as last peer is just a continuation for extra AllowedIPs */
+	peer_c = c_list_last_entry (peers, WireGuardPeerConstruct, lst);
+	if (   peer_c
+	    && !memcmp (nla_data (tb[WGPEER_A_PUBLIC_KEY]), peer_c->data.public_key, NMP_WIREGUARD_PUBLIC_KEY_LEN)) {
+		G_STATIC_ASSERT_EXPR (NMP_WIREGUARD_PUBLIC_KEY_LEN == sizeof (peer_c->data.public_key));
+		/* this message is a continuation of the previous peer.
+		 * Only parse WGPEER_A_ALLOWEDIPS below. */
+	}
+	else {
+		/* otherwise, start a new peer */
+		peer_c = g_slice_new0 (WireGuardPeerConstruct);
+		c_list_link_tail (peers, &peer_c->lst);
+
+		nla_memcpy (&peer_c->data.public_key, tb[WGPEER_A_PUBLIC_KEY], sizeof (peer_c->data.public_key));
+
+		if (tb[WGPEER_A_PRESHARED_KEY]) {
+			nla_memcpy (&peer_c->data.preshared_key, tb[WGPEER_A_PRESHARED_KEY], sizeof (peer_c->data.preshared_key));
+			/* FIXME(netlink-bzero-secret) */
+			nm_explicit_bzero (nla_data (tb[WGPEER_A_PRESHARED_KEY]),
+			                   nla_len (tb[WGPEER_A_PRESHARED_KEY]));
+		}
+		if (tb[WGPEER_A_ENDPOINT]) {
+			const struct sockaddr *addr = nla_data (tb[WGPEER_A_ENDPOINT]);
+			unsigned short family;
+
+			G_STATIC_ASSERT (sizeof (addr->sa_family) == sizeof (family));
+			memcpy (&family, &addr->sa_family, sizeof (addr->sa_family));
+
+			if (   family == AF_INET
+			    && nla_len (tb[WGPEER_A_ENDPOINT]) == sizeof (struct sockaddr_in)) {
+				const struct sockaddr_in *addr4 = (const struct sockaddr_in *) addr;
+
+				peer_c->data.endpoint_family = AF_INET;
+				peer_c->data.endpoint_port = unaligned_read_be16 (&addr4->sin_port);
+				peer_c->data.endpoint_addr.addr4 = unaligned_read_ne32 (&addr4->sin_addr.s_addr);
+				memcpy (&peer_c->data.endpoint_addr.addr4, &addr4->sin_addr.s_addr, 4);
+			} else if (   family == AF_INET6
+			           && nla_len (tb[WGPEER_A_ENDPOINT]) == sizeof (struct sockaddr_in6)) {
+				const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *) addr;
+
+				peer_c->data.endpoint_family = AF_INET6;
+				peer_c->data.endpoint_port = unaligned_read_be16 (&addr6->sin6_port);
+				memcpy (&peer_c->data.endpoint_addr.addr6, &addr6->sin6_addr, 16);
+			}
+		}
+		if (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL])
+			peer_c->data.persistent_keepalive_interval = nla_get_u64 (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
+		if (tb[WGPEER_A_LAST_HANDSHAKE_TIME])
+			nla_memcpy (&peer_c->data.last_handshake_time, tb[WGPEER_A_LAST_HANDSHAKE_TIME], sizeof (peer_c->data.last_handshake_time));
+		if (tb[WGPEER_A_RX_BYTES])
+			peer_c->data.rx_bytes = nla_get_u64 (tb[WGPEER_A_RX_BYTES]);
+		if (tb[WGPEER_A_TX_BYTES])
+			peer_c->data.tx_bytes = nla_get_u64 (tb[WGPEER_A_TX_BYTES]);
+	}
+
+	if (tb[WGPEER_A_ALLOWEDIPS]) {
+		struct nlattr *attr;
+		int rem;
+		GArray *allowed_ips = *p_allowed_ips;
+
+		nla_for_each_nested (attr, tb[WGPEER_A_ALLOWEDIPS], rem) {
+			if (!allowed_ips) {
+				allowed_ips = g_array_new (FALSE, FALSE, sizeof (NMPWireGuardAllowedIP));
+				*p_allowed_ips = allowed_ips;
+				g_array_set_size (allowed_ips, 1);
+			} else
+				g_array_set_size (allowed_ips, allowed_ips->len + 1);
+
+			if (!_wireguard_update_from_allowed_ips_nla (&g_array_index (allowed_ips,
+			                                                             NMPWireGuardAllowedIP,
+			                                                             allowed_ips->len - 1),
+			                                            attr)) {
+				/* we ignore the error of parsing one allowed-ip. */
+				g_array_set_size (allowed_ips, allowed_ips->len - 1);
+				continue;
+			}
+
+			if (!peer_c->data._construct_idx_end)
+				peer_c->data._construct_idx_start = allowed_ips->len - 1;
+			peer_c->data._construct_idx_end = allowed_ips->len;
+		}
+	}
+
+	return TRUE;
+}
+
+typedef struct {
+	const int ifindex;
+	NMPObject *obj;
+	CList peers;
+	GArray *allowed_ips;
+} WireGuardParseData;
+
+static int
+_wireguard_get_device_cb (struct nl_msg *msg, void *arg)
+{
+	static const struct nla_policy policy[WGDEVICE_A_MAX + 1] = {
+		[WGDEVICE_A_IFINDEX]     = { .type = NLA_U32 },
+		[WGDEVICE_A_IFNAME]      = { .type = NLA_NUL_STRING, .maxlen = IFNAMSIZ },
+		[WGDEVICE_A_PRIVATE_KEY] = { },
+		[WGDEVICE_A_PUBLIC_KEY]  = { },
+		[WGDEVICE_A_FLAGS]       = { .type = NLA_U32 },
+		[WGDEVICE_A_LISTEN_PORT] = { .type = NLA_U16 },
+		[WGDEVICE_A_FWMARK]      = { .type = NLA_U32 },
+		[WGDEVICE_A_PEERS]       = { .type = NLA_NESTED },
+	};
+	WireGuardParseData *parse_data = arg;
+	struct nlattr *tb[WGDEVICE_A_MAX + 1];
+	int nlerr;
+
+	nlerr = genlmsg_parse (nlmsg_hdr (msg), 0, tb, WGDEVICE_A_MAX, policy);
+	if (nlerr < 0)
+		return NL_SKIP;
+
+	if (tb[WGDEVICE_A_IFINDEX]) {
+		int ifindex;
+
+		ifindex = (int) nla_get_u32 (tb[WGDEVICE_A_IFINDEX]);
+		if (   ifindex <= 0
+		    || parse_data->ifindex != ifindex)
+			return NL_SKIP;
+	} else {
+		if (!parse_data->obj)
+			return NL_SKIP;
+	}
+
+	if (parse_data->obj) {
+		/* we already have an object instance. This means the netlink message
+		 * is a continuation, only providing more WGDEVICE_A_PEERS data below. */
+	} else {
+		NMPObject *obj;
+		NMPlatformLnkWireGuard *props;
+
+		obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_WIREGUARD, NULL);
+		props = &obj->lnk_wireguard;
+
+		if (tb[WGDEVICE_A_PRIVATE_KEY]) {
+			nla_memcpy (props->private_key, tb[WGDEVICE_A_PRIVATE_KEY], sizeof (props->private_key));
+			/* FIXME(netlink-bzero-secret): extend netlink library to wipe memory. For now,
+			 * just hack it here (yes, this does not cover all places where the
+			 * private key was copied). */
+			nm_explicit_bzero (nla_data (tb[WGDEVICE_A_PRIVATE_KEY]),
+			                   nla_len (tb[WGDEVICE_A_PRIVATE_KEY]));
+		}
+		if (tb[WGDEVICE_A_PUBLIC_KEY])
+			nla_memcpy (props->public_key, tb[WGDEVICE_A_PUBLIC_KEY], sizeof (props->public_key));
+		if (tb[WGDEVICE_A_LISTEN_PORT])
+			props->listen_port = nla_get_u16 (tb[WGDEVICE_A_LISTEN_PORT]);
+		if (tb[WGDEVICE_A_FWMARK])
+			props->fwmark = nla_get_u32 (tb[WGDEVICE_A_FWMARK]);
+
+		parse_data->obj = obj;
+	}
+
+	if (tb[WGDEVICE_A_PEERS]) {
+		struct nlattr *attr;
+		int rem;
+
+		nla_for_each_nested (attr, tb[WGDEVICE_A_PEERS], rem) {
+			if (!_wireguard_update_from_peers_nla (&parse_data->peers, &parse_data->allowed_ips, attr)) {
+				/* we ignore the error of parsing one peer.
+				 * _wireguard_update_from_peers_nla() leaves the @peers array in the
+				 * desired state. */
+			}
+		}
+	}
+
+	return NL_OK;
+}
+
+static const NMPObject *
+_wireguard_read_info (NMPlatform *platform /* used only as logging context */,
+                     struct nl_sock *genl,
+                     int wireguard_family_id,
+                     int ifindex)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	NMPObject *obj = NULL;
+	WireGuardPeerConstruct *peer_c;
+	WireGuardPeerConstruct *peer_c_safe;
+	gs_unref_array GArray *allowed_ips = NULL;
+	WireGuardParseData parse_data = {
+		.ifindex = ifindex,
+	};
+	guint i;
+
+	nm_assert (genl);
+	nm_assert (wireguard_family_id >= 0);
+	nm_assert (ifindex > 0);
+
+	msg = nlmsg_alloc ();
+
+	if (!genlmsg_put (msg,
+	                  NL_AUTO_PORT,
+	                  NL_AUTO_SEQ,
+	                  wireguard_family_id,
+	                  0,
+	                  NLM_F_DUMP,
+	                  WG_CMD_GET_DEVICE,
+	                  1))
+		return NULL;
+
+	NLA_PUT_U32 (msg, WGDEVICE_A_IFINDEX, (guint32) ifindex);
+
+	if (nl_send_auto (genl, msg) < 0)
+		return NULL;
+
+	c_list_init (&parse_data.peers);
+
+	/* we ignore errors, and return whatever we could successfully
+	 * parse. */
+	nl_recvmsgs (genl,
+	             &((const struct nl_cb) {
+	                 .valid_cb = _wireguard_get_device_cb,
+	                 .valid_arg = (gpointer) &parse_data,
+	             }));
+
+	/* unpack: transfer ownership */
+	obj = parse_data.obj;
+	allowed_ips = parse_data.allowed_ips;
+
+	if (!obj) {
+		while ((peer_c = c_list_first_entry (&parse_data.peers, WireGuardPeerConstruct, lst))) {
+			c_list_unlink_stale (&peer_c->lst);
+			nm_explicit_bzero (&peer_c->data.preshared_key, sizeof (peer_c->data.preshared_key));
+			g_slice_free (WireGuardPeerConstruct, peer_c);
+		}
+		return NULL;
+	}
+
+	/* we receive peers/allowed-ips possibly in separate netlink messages. Hence, while
+	 * parsing the dump, we don't know upfront how many peers/allowed-ips we will receive.
+	 *
+	 * We solve that, by collecting all peers with a CList. It's done this way,
+	 * because a GArray would require growing the array, but we want to bzero()
+	 * the preshared-key of each peer while reallocating. The CList apprach avoids
+	 * that.
+	 *
+	 * For allowed-ips, we instead track one GArray, which are all appended
+	 * there. The realloc/resize of the GArray is fine there. However,
+	 * while we build the GArray, we don't yet have the final pointers.
+	 * Hence, while constructing, we track the indexes with peer->_construct_idx_*
+	 * fields. These indexes must be convered to actual pointers blow.
+	 *
+	 * This is all done during parsing. In the final NMPObjectLnkWireGuard we
+	 * don't want the CList anymore and repackage the NMPObject tightly. The
+	 * reason is, that NMPObject instances are immutable and long-living. Spend
+	 * a bit effort below during construction to obtain a most suitable representation
+	 * in this regard. */
+	obj->_lnk_wireguard.peers_len = c_list_length (&parse_data.peers);
+	obj->_lnk_wireguard.peers = obj->_lnk_wireguard.peers_len > 0
+	                            ? g_new (NMPWireGuardPeer, obj->_lnk_wireguard.peers_len)
+	                            : NULL;
+
+	/* duplicate allowed_ips instead of using the pointer. The GArray possibly has more
+	 * space allocated then we need, and we want to get rid of this excess buffer.
+	 * Note that NMPObject instance is possibly put into the cache and long-living. */
+	obj->_lnk_wireguard._allowed_ips_buf_len = allowed_ips ? allowed_ips->len : 0u;
+	obj->_lnk_wireguard._allowed_ips_buf = obj->_lnk_wireguard._allowed_ips_buf_len > 0
+	                                       ? (NMPWireGuardAllowedIP *) nm_memdup (allowed_ips->data,
+	                                                                              sizeof (NMPWireGuardAllowedIP) * allowed_ips->len)
+	                                       : NULL;
+
+	i = 0;
+	c_list_for_each_entry_safe (peer_c, peer_c_safe, &parse_data.peers, lst) {
+		NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &obj->_lnk_wireguard.peers[i++];
+
+		*peer = peer_c->data;
+
+		c_list_unlink_stale (&peer_c->lst);
+		nm_explicit_bzero (&peer_c->data.preshared_key, sizeof (peer_c->data.preshared_key));
+		g_slice_free (WireGuardPeerConstruct, peer_c);
+
+		if (peer->_construct_idx_end != 0) {
+			guint len;
+
+			nm_assert (obj->_lnk_wireguard._allowed_ips_buf);
+			nm_assert (peer->_construct_idx_end > peer->_construct_idx_start);
+			nm_assert (peer->_construct_idx_start < obj->_lnk_wireguard._allowed_ips_buf_len);
+			nm_assert (peer->_construct_idx_end <= obj->_lnk_wireguard._allowed_ips_buf_len);
+
+			len = peer->_construct_idx_end - peer->_construct_idx_start;
+			peer->allowed_ips = &obj->_lnk_wireguard._allowed_ips_buf[peer->_construct_idx_start];
+			peer->allowed_ips_len = len;
+		} else {
+			nm_assert (!peer->_construct_idx_start);
+			nm_assert (!peer->_construct_idx_end);
+			peer->allowed_ips = NULL;
+			peer->allowed_ips_len = 0;
+		}
+	}
+
+	return obj;
+
+nla_put_failure:
+	g_return_val_if_reached (NULL);
+}
+
+/*****************************************************************************/
+
 /* Copied and heavily modified from libnl3's link_msg_parser(). */
 static NMPObject *
 _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr *nlh, gboolean id_only)
@@ -1707,37 +2329,39 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	const char *nl_info_kind = NULL;
 	int err;
 	nm_auto_nmpobj NMPObject *obj = NULL;
-	NMPObject *obj_result = NULL;
 	gboolean completed_from_cache_val = FALSE;
 	gboolean *completed_from_cache = cache ? &completed_from_cache_val : NULL;
 	const NMPObject *link_cached = NULL;
-	NMPObject *lnk_data = NULL;
+	const NMPObject *lnk_data = NULL;
 	gboolean address_complete_from_cache = TRUE;
 	gboolean lnk_data_complete_from_cache = TRUE;
+	gboolean need_ext_data = FALSE;
 	gboolean af_inet6_token_valid = FALSE;
 	gboolean af_inet6_addr_gen_mode_valid = FALSE;
 
 	if (!nlmsg_valid_hdr (nlh, sizeof (*ifi)))
 		return NULL;
-	ifi = nlmsg_data(nlh);
+	ifi = nlmsg_data (nlh);
 
 	if (ifi->ifi_family != AF_UNSPEC)
 		return NULL;
+	if (ifi->ifi_index <= 0)
+		return NULL;
 
 	obj = nmp_object_new_link (ifi->ifi_index);
 
 	if (id_only)
-		goto id_only_handled;
+		return g_steal_pointer (&obj);
 
 	err = nlmsg_parse (nlh, sizeof (*ifi), tb, IFLA_MAX, policy);
 	if (err < 0)
-		goto errout;
+		return NULL;
 
 	if (!tb[IFLA_IFNAME])
-		goto errout;
+		return NULL;
 	nla_strlcpy(obj->link.name, tb[IFLA_IFNAME], IFNAMSIZ);
 	if (!obj->link.name[0])
-		goto errout;
+		return NULL;
 
 	if (!tb[IFLA_MTU]) {
 		/* Kernel has two places that send RTM_GETLINK messages:
@@ -1752,14 +2376,14 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 		 * To some extent this is a hack and correct approach is to
 		 * merge objects per-field.
 		 */
-		goto errout;
+		return NULL;
 	}
 	obj->link.mtu = nla_get_u32 (tb[IFLA_MTU]);
 
 	if (tb[IFLA_LINKINFO]) {
 		err = nla_parse_nested (li, IFLA_INFO_MAX, tb[IFLA_LINKINFO], policy_link_info);
 		if (err < 0)
-			goto errout;
+			return NULL;
 
 		if (li[IFLA_INFO_KIND])
 			nl_info_kind = nla_get_string (li[IFLA_INFO_KIND]);
@@ -1838,6 +2462,7 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 
 	switch (obj->link.type) {
 	case NM_LINK_TYPE_GRE:
+	case NM_LINK_TYPE_GRETAP:
 		lnk_data = _parse_lnk_gre (nl_info_kind, nl_info_data);
 		break;
 	case NM_LINK_TYPE_INFINIBAND:
@@ -1846,6 +2471,10 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	case NM_LINK_TYPE_IP6TNL:
 		lnk_data = _parse_lnk_ip6tnl (nl_info_kind, nl_info_data);
 		break;
+	case NM_LINK_TYPE_IP6GRE:
+	case NM_LINK_TYPE_IP6GRETAP:
+		lnk_data = _parse_lnk_ip6gre (nl_info_kind, nl_info_data);
+		break;
 	case NM_LINK_TYPE_IPIP:
 		lnk_data = _parse_lnk_ipip (nl_info_kind, nl_info_data);
 		break;
@@ -1868,6 +2497,15 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	case NM_LINK_TYPE_VXLAN:
 		lnk_data = _parse_lnk_vxlan (nl_info_kind, nl_info_data);
 		break;
+	case NM_LINK_TYPE_WIFI:
+	case NM_LINK_TYPE_OLPC_MESH:
+	case NM_LINK_TYPE_WPAN:
+		need_ext_data = TRUE;
+		lnk_data_complete_from_cache = FALSE;
+		break;
+	case NM_LINK_TYPE_WIREGUARD:
+		lnk_data_complete_from_cache = TRUE;
+		break;
 	default:
 		lnk_data_complete_from_cache = FALSE;
 		break;
@@ -1875,12 +2513,14 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 
 	if (   completed_from_cache
 	    && (   lnk_data_complete_from_cache
+	        || need_ext_data
 	        || address_complete_from_cache
 	        || !af_inet6_token_valid
 	        || !af_inet6_addr_gen_mode_valid
 	        || !tb[IFLA_STATS64])) {
 		_lookup_cached_link (cache, obj->link.ifindex, completed_from_cache, &link_cached);
-		if (link_cached) {
+		if (   link_cached
+		    && link_cached->_link.netlink.is_in_netlink) {
 			if (   lnk_data_complete_from_cache
 			    && link_cached->link.type == obj->link.type
 			    && link_cached->_link.netlink.lnk
@@ -1893,8 +2533,16 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 				 * Also, sometimes the info-data is missing for updates. In this case
 				 * we want to keep the previously received lnk_data. */
 				nmp_object_unref (lnk_data);
-				lnk_data = (NMPObject *) nmp_object_ref (link_cached->_link.netlink.lnk);
+				lnk_data = nmp_object_ref (link_cached->_link.netlink.lnk);
 			}
+
+			if (   need_ext_data
+			    && link_cached->link.type == obj->link.type
+			    && link_cached->_link.ext_data) {
+				/* Prefer reuse of existing ext_data object */
+				obj->_link.ext_data = g_object_ref (link_cached->_link.ext_data);
+			}
+
 			if (address_complete_from_cache)
 				obj->link.addr = link_cached->link.addr;
 			if (!af_inet6_token_valid)
@@ -1912,12 +2560,71 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 
 	obj->_link.netlink.lnk = lnk_data;
 
+	if (   need_ext_data
+	    && obj->_link.ext_data == NULL) {
+		switch (obj->link.type) {
+		case NM_LINK_TYPE_WIFI:
+			obj->_link.ext_data = (GObject *) nm_wifi_utils_new (ifi->ifi_index,
+			                                                     _genl_sock (NM_LINUX_PLATFORM (platform)),
+			                                                     TRUE);
+			break;
+		case NM_LINK_TYPE_OLPC_MESH:
+#if HAVE_WEXT
+			/* The kernel driver now uses nl80211, but we force use of WEXT because
+			 * the cfg80211 interactions are not quite ready to support access to
+			 * mesh control through nl80211 just yet.
+			 */
+			obj->_link.ext_data = (GObject *) nm_wifi_utils_wext_new (ifi->ifi_index, FALSE);
+#endif
+			break;
+		case NM_LINK_TYPE_WPAN:
+			obj->_link.ext_data = (GObject *) nm_wpan_utils_new (ifi->ifi_index,
+			                                                     _genl_sock (NM_LINUX_PLATFORM (platform)),
+			                                                     TRUE);
+			break;
+		default:
+			g_assert_not_reached ();
+		}
+	}
+
+	if (obj->link.type == NM_LINK_TYPE_WIREGUARD) {
+		const NMPObject *lnk_data_new = NULL;
+		struct nl_sock *genl = NM_LINUX_PLATFORM_GET_PRIVATE (platform)->genl;
+
+		/* The WireGuard kernel module does not yet send link update
+		 * notifications, so we don't actually update the cache. For
+		 * now, always refetch link data here. */
+
+		_lookup_cached_link (cache, obj->link.ifindex, completed_from_cache, &link_cached);
+		if (   link_cached
+		    && link_cached->_link.netlink.is_in_netlink
+		    && link_cached->link.type == NM_LINK_TYPE_WIREGUARD)
+			obj->_link.wireguard_family_id = link_cached->_link.wireguard_family_id;
+		else
+			obj->_link.wireguard_family_id = -1;
+
+		if (obj->_link.wireguard_family_id < 0)
+			obj->_link.wireguard_family_id = genl_ctrl_resolve (genl, "wireguard");
+
+		if (obj->_link.wireguard_family_id >= 0) {
+			lnk_data_new = _wireguard_read_info (platform,
+			                                     genl,
+			                                     obj->_link.wireguard_family_id,
+			                                     obj->link.ifindex);
+		}
+
+		if (   lnk_data_new
+		    && obj->_link.netlink.lnk
+		    && nmp_object_equal (obj->_link.netlink.lnk, lnk_data_new))
+			nmp_object_unref (lnk_data_new);
+		else {
+			nmp_object_unref (obj->_link.netlink.lnk);
+			obj->_link.netlink.lnk = lnk_data_new;
+		}
+	}
+
 	obj->_link.netlink.is_in_netlink = TRUE;
-id_only_handled:
-	obj_result = obj;
-	obj = NULL;
-errout:
-	return obj_result;
+	return g_steal_pointer (&obj);
 }
 
 /* Copied and heavily modified from libnl3's addr_msg_parser(). */
@@ -1934,7 +2641,6 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 	int err;
 	gboolean is_v4;
 	nm_auto_nmpobj NMPObject *obj = NULL;
-	NMPObject *obj_result = NULL;
 	int addr_len;
 	guint32 lifetime, preferred, timestamp;
 
@@ -1943,19 +2649,19 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 	ifa = nlmsg_data(nlh);
 
 	if (!NM_IN_SET (ifa->ifa_family, AF_INET, AF_INET6))
-		goto errout;
+		return NULL;
 	is_v4 = ifa->ifa_family == AF_INET;
 
 	err = nlmsg_parse (nlh, sizeof(*ifa), tb, IFA_MAX, policy);
 	if (err < 0)
-		goto errout;
+		return NULL;
 
 	addr_len = is_v4
 	           ? sizeof (in_addr_t)
 	           : sizeof (struct in6_addr);
 
 	if (ifa->ifa_prefixlen > (is_v4 ? 32 : 128))
-		goto errout;
+		return NULL;
 
 	/*****************************************************************/
 
@@ -1964,8 +2670,8 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 	obj->ip_address.ifindex = ifa->ifa_index;
 	obj->ip_address.plen = ifa->ifa_prefixlen;
 
-	_check_addr_or_errout (tb, IFA_ADDRESS, addr_len);
-	_check_addr_or_errout (tb, IFA_LOCAL, addr_len);
+	_check_addr_or_return_null (tb, IFA_ADDRESS, addr_len);
+	_check_addr_or_return_null (tb, IFA_LOCAL, addr_len);
 	if (is_v4) {
 		/* For IPv4, kernel omits IFA_LOCAL/IFA_ADDRESS if (and only if) they
 		 * are effectively 0.0.0.0 (all-zero). */
@@ -2029,10 +2735,7 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 	                         &obj->ip_address.lifetime,
 	                         &obj->ip_address.preferred);
 
-	obj_result = obj;
-	obj = NULL;
-errout:
-	return obj_result;
+	return g_steal_pointer (&obj);
 }
 
 /* Copied and heavily modified from libnl3's rtnl_route_parse() and parse_multipath(). */
@@ -2055,7 +2758,6 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	int err;
 	gboolean is_v4;
 	nm_auto_nmpobj NMPObject *obj = NULL;
-	NMPObject *obj_result = NULL;
 	int addr_len;
 	struct {
 		gboolean is_present;
@@ -2074,14 +2776,14 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	 *****************************************************************/
 
 	if (!NM_IN_SET (rtm->rtm_family, AF_INET, AF_INET6))
-		goto errout;
+		return NULL;
 
 	if (rtm->rtm_type != RTN_UNICAST)
-		goto errout;
+		return NULL;
 
 	err = nlmsg_parse (nlh, sizeof (struct rtmsg), tb, RTA_MAX, policy);
 	if (err < 0)
-		goto errout;
+		return NULL;
 
 	/*****************************************************************/
 
@@ -2091,7 +2793,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	           : sizeof (struct in6_addr);
 
 	if (rtm->rtm_dst_len > (is_v4 ? 32 : 128))
-		goto errout;
+		return NULL;
 
 	/*****************************************************************
 	 * parse nexthops. Only handle routes with one nh.
@@ -2107,7 +2809,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 
 			if (nh.is_present) {
 				/* we don't support multipath routes. */
-				goto errout;
+				return NULL;
 			}
 			nh.is_present = TRUE;
 
@@ -2121,9 +2823,9 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 				                 rtnh->rtnh_len - sizeof (*rtnh),
 				                 policy);
 				if (err < 0)
-					goto errout;
+					return NULL;
 
-				if (_check_addr_or_errout (ntb, RTA_GATEWAY, addr_len))
+				if (_check_addr_or_return_null (ntb, RTA_GATEWAY, addr_len))
 					memcpy (&nh.gateway, nla_data (ntb[RTA_GATEWAY]), addr_len);
 			}
 
@@ -2140,7 +2842,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 
 		if (tb[RTA_OIF])
 			ifindex = nla_get_u32 (tb[RTA_OIF]);
-		if (_check_addr_or_errout (tb, RTA_GATEWAY, addr_len))
+		if (_check_addr_or_return_null (tb, RTA_GATEWAY, addr_len))
 			memcpy (&gateway, nla_data (tb[RTA_GATEWAY]), addr_len);
 
 		if (!nh.is_present) {
@@ -2154,10 +2856,10 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 			 * verify that it is a duplicate and ignore old-style nexthop. */
 			if (   nh.ifindex != ifindex
 			    || memcmp (&nh.gateway, &gateway, addr_len) != 0)
-				goto errout;
+				return NULL;
 		}
 	} else if (!nh.is_present)
-		goto errout;
+		return NULL;
 
 	/*****************************************************************/
 
@@ -2176,7 +2878,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 
 		err = nla_parse_nested (mtb, RTAX_MAX, tb[RTA_METRICS], rtax_policy);
 		if (err < 0)
-			goto errout;
+			return NULL;
 
 		if (mtb[RTAX_LOCK])
 			lock = nla_get_u32 (mtb[RTAX_LOCK]);
@@ -2204,7 +2906,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 
 	obj->ip_route.ifindex = nh.ifindex;
 
-	if (_check_addr_or_errout (tb, RTA_DST, addr_len))
+	if (_check_addr_or_return_null (tb, RTA_DST, addr_len))
 		memcpy (obj->ip_route.network_ptr, nla_data (tb[RTA_DST]), addr_len);
 
 	obj->ip_route.plen = rtm->rtm_dst_len;
@@ -2220,7 +2922,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	if (is_v4)
 		obj->ip4_route.scope_inv = nm_platform_route_scope_inv (rtm->rtm_scope);
 
-	if (_check_addr_or_errout (tb, RTA_PREFSRC, addr_len)) {
+	if (_check_addr_or_return_null (tb, RTA_PREFSRC, addr_len)) {
 		if (is_v4)
 			memcpy (&obj->ip4_route.pref_src, nla_data (tb[RTA_PREFSRC]), addr_len);
 		else
@@ -2231,7 +2933,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 		obj->ip4_route.tos = rtm->rtm_tos;
 	else {
 		if (tb[RTA_SRC]) {
-			_check_addr_or_errout (tb, RTA_SRC, addr_len);
+			_check_addr_or_return_null (tb, RTA_SRC, addr_len);
 			memcpy (&obj->ip6_route.src, nla_data (tb[RTA_SRC]), addr_len);
 		}
 		obj->ip6_route.src_plen = rtm->rtm_src_len;
@@ -2261,10 +2963,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	obj->ip_route.r_rtm_flags = rtm->rtm_flags;
 	obj->ip_route.rt_source = nmp_utils_ip_config_source_from_rtprot (rtm->rtm_protocol);
 
-	obj_result = obj;
-	obj = NULL;
-errout:
-	return obj_result;
+	return g_steal_pointer (&obj);
 }
 
 static NMPObject *
@@ -2941,103 +3640,16 @@ nla_put_failure:
 	g_return_val_if_reached (NULL);
 }
 
-/******************************************************************
- * NMPlatform types and functions
- ******************************************************************/
-
-typedef enum {
-	DELAYED_ACTION_RESPONSE_TYPE_VOID                       = 0,
-	DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS    = 1,
-	DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET                  = 2,
-} DelayedActionWaitForNlResponseType;
-
-typedef struct {
-	guint32 seq_number;
-	WaitForNlResponseResult seq_result;
-	DelayedActionWaitForNlResponseType response_type;
-	gint64 timeout_abs_ns;
-	WaitForNlResponseResult *out_seq_result;
-	char **out_errmsg;
-	union {
-		gint *out_refresh_all_in_progess;
-		NMPObject **out_route_get;
-		gpointer out_data;
-	} response;
-} DelayedActionWaitForNlResponseData;
-
-typedef struct {
-	struct nl_sock *nlh;
-	guint32 nlh_seq_next;
-#ifdef NM_MORE_LOGGING
-	guint32 nlh_seq_last_handled;
-#endif
-	guint32 nlh_seq_last_seen;
-	GIOChannel *event_channel;
-	guint event_id;
-
-	bool pruning[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
-
-	bool sysctl_get_warned;
-	GHashTable *sysctl_get_prev_values;
-
-	NMUdevClient *udev_client;
-
-	struct {
-		/* which delayed actions are scheduled, as marked in @flags.
-		 * Some types have additional arguments in the fields below. */
-		DelayedActionType flags;
-
-		/* counter that a refresh all action is in progress, separated
-		 * by type. */
-		gint refresh_all_in_progess[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
-
-		GPtrArray *list_master_connected;
-		GPtrArray *list_refresh_link;
-		GArray *list_wait_for_nl_response;
-
-		gint is_handling;
-	} delayed_action;
-
-	GHashTable *wifi_data;
-} NMLinuxPlatformPrivate;
-
-struct _NMLinuxPlatform {
-	NMPlatform parent;
-	NMLinuxPlatformPrivate _priv;
-};
-
-struct _NMLinuxPlatformClass {
-	NMPlatformClass parent;
-};
-
-G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
-
-#define NM_LINUX_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMLinuxPlatform, NM_IS_LINUX_PLATFORM, NMPlatform)
+/*****************************************************************************/
 
-NMPlatform *
-nm_linux_platform_new (gboolean log_with_ptr, gboolean netns_support)
+static struct nl_sock *
+_genl_sock (NMLinuxPlatform *platform)
 {
-	gboolean use_udev = FALSE;
-
-	if (   nmp_netns_is_initial ()
-	    && access ("/sys", W_OK) == 0)
-		use_udev = TRUE;
-
-	return g_object_new (NM_TYPE_LINUX_PLATFORM,
-	                     NM_PLATFORM_LOG_WITH_PTR, log_with_ptr,
-	                     NM_PLATFORM_USE_UDEV, use_udev,
-	                     NM_PLATFORM_NETNS_SUPPORT, netns_support,
-	                     NULL);
-}
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 
-void
-nm_linux_platform_setup (void)
-{
-	nm_platform_setup (nm_linux_platform_new (FALSE, FALSE));
+	return priv->genl;
 }
 
-/*****************************************************************************/
-
 #define ASSERT_SYSCTL_ARGS(pathid, dirfd, path) \
 	G_STMT_START { \
 		const char *const _pathid = (pathid); \
@@ -3064,7 +3676,9 @@ _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *pathid, int dirfd, c
 	char *contents;
 	gs_free char *value_escaped = g_strescape (value, NULL);
 
-	if (nm_utils_file_get_contents (dirfd, path, 1*1024*1024, &contents, NULL, &error) < 0) {
+	if (nm_utils_file_get_contents (dirfd, path, 1*1024*1024,
+	                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+	                                &contents, NULL, &error) < 0) {
 		_LOGD ("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", pathid, value_escaped, error->message);
 		g_clear_error (&error);
 		return;
@@ -3281,7 +3895,9 @@ sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 		pathid = path;
 	}
 
-	if (nm_utils_file_get_contents (dirfd, path, 1*1024*1024, &contents, NULL, &error) < 0) {
+	if (nm_utils_file_get_contents (dirfd, path, 1*1024*1024,
+	                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+	                                &contents, NULL, &error) < 0) {
 		/* We assume FAILED means EOPNOTSUP */
 		if (   g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)
 		    || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NODEV)
@@ -3452,7 +4068,7 @@ delayed_action_refresh_all_in_progress (NMPlatform *platform, DelayedActionType
 	if (NM_FLAGS_ANY (priv->delayed_action.flags, action_type))
 		return TRUE;
 
-	if (priv->delayed_action.refresh_all_in_progess[delayed_action_refresh_all_to_idx (action_type)] > 0)
+	if (priv->delayed_action.refresh_all_in_progress[delayed_action_refresh_all_to_idx (action_type)] > 0)
 		return TRUE;
 
 	return FALSE;
@@ -3482,10 +4098,10 @@ delayed_action_wait_for_nl_response_complete (NMPlatform *platform,
 	case DELAYED_ACTION_RESPONSE_TYPE_VOID:
 		break;
 	case DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS:
-		if (data->response.out_refresh_all_in_progess) {
-			nm_assert (*data->response.out_refresh_all_in_progess > 0);
-			*data->response.out_refresh_all_in_progess -= 1;
-			data->response.out_refresh_all_in_progess = NULL;
+		if (data->response.out_refresh_all_in_progress) {
+			nm_assert (*data->response.out_refresh_all_in_progress > 0);
+			*data->response.out_refresh_all_in_progress -= 1;
+			data->response.out_refresh_all_in_progress = NULL;
 		}
 		break;
 	case DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET:
@@ -3510,7 +4126,7 @@ delayed_action_wait_for_nl_response_complete_check (NMPlatform *platform,
 	guint i;
 	guint32 next_seq_number = 0;
 	gint64 next_timeout_abs_ns = 0;
-	gint now_ns = 0;
+	int now_ns = 0;
 
 	for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; ) {
 		const DelayedActionWaitForNlResponseData *data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
@@ -3932,7 +4548,10 @@ cache_on_change (NMPlatform *platform,
 
 			if (   !obj_new->_link.netlink.lnk
 			    && NM_IN_SET (obj_new->link.type, NM_LINK_TYPE_GRE,
+			                                      NM_LINK_TYPE_GRETAP,
 			                                      NM_LINK_TYPE_IP6TNL,
+			                                      NM_LINK_TYPE_IP6GRE,
+			                                      NM_LINK_TYPE_IP6GRETAP,
 			                                      NM_LINK_TYPE_INFINIBAND,
 			                                      NM_LINK_TYPE_MACVLAN,
 			                                      NM_LINK_TYPE_MACVLAN,
@@ -4205,11 +4824,11 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 		const NMPClass *klass = nmp_class_from_type (obj_type);
 		nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 		int nle;
-		gint *out_refresh_all_in_progess;
+		int *out_refresh_all_in_progress;
 
-		out_refresh_all_in_progess = &priv->delayed_action.refresh_all_in_progess[delayed_action_refresh_all_to_idx (iflags)];
-		nm_assert (*out_refresh_all_in_progess >= 0);
-		*out_refresh_all_in_progess += 1;
+		out_refresh_all_in_progress = &priv->delayed_action.refresh_all_in_progress[delayed_action_refresh_all_to_idx (iflags)];
+		nm_assert (*out_refresh_all_in_progress >= 0);
+		*out_refresh_all_in_progress += 1;
 
 		/* clear any delayed action that request a refresh of this object type. */
 		priv->delayed_action.flags &= ~iflags;
@@ -4243,9 +4862,9 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 		if (nle < 0)
 			continue;
 
-		if (_nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progess) < 0) {
-			nm_assert (*out_refresh_all_in_progess > 0);
-			*out_refresh_all_in_progess -= 1;
+		if (_nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progress) < 0) {
+			nm_assert (*out_refresh_all_in_progress > 0);
+			*out_refresh_all_in_progress -= 1;
 		}
 	}
 }
@@ -4274,10 +4893,10 @@ event_seq_check_refresh_all (NMPlatform *platform, guint32 seq_number)
 			data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
 
 			if (   data->response_type == DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS
-			    && data->response.out_refresh_all_in_progess
+			    && data->response.out_refresh_all_in_progress
 			    && data->seq_number == priv->nlh_seq_last_seen) {
-				*data->response.out_refresh_all_in_progess -= 1;
-				data->response.out_refresh_all_in_progess = NULL;
+				*data->response.out_refresh_all_in_progress -= 1;
+				data->response.out_refresh_all_in_progress = NULL;
 				break;
 			}
 		}
@@ -4318,7 +4937,7 @@ event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResu
 		}
 	}
 
-#ifdef NM_MORE_LOGGING
+#if NM_MORE_LOGGING
 	if (seq_number != priv->nlh_seq_last_handled)
 		_LOGt ("netlink: recvmsg: unwaited sequence number %u", seq_number);
 	priv->nlh_seq_last_handled = seq_number;
@@ -4741,6 +5360,9 @@ retry:
 	} else if (NM_IN_SET (-((int) seq_result), ENODEV)) {
 		log_level = LOGL_DEBUG;
 		result = NM_PLATFORM_ERROR_NOT_FOUND;
+	} else if (-((int) seq_result) == EAFNOSUPPORT) {
+		log_level = LOGL_DEBUG;
+		result = NM_PLATFORM_ERROR_OPNOTSUPP;
 	} else {
 		log_level = LOGL_WARN;
 		result = NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -4841,8 +5463,6 @@ link_set_netns (NMPlatform *platform,
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	_LOGD ("link: move link %d to network namespace with fd %d", ifindex, netns_fd);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -5031,7 +5651,6 @@ static NMPlatformError
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size_t length)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	gs_free char *mac = NULL;
 	const ChangeLinkData d = {
 		.set_address = {
 			.address = address,
@@ -5042,10 +5661,6 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size
 	if (!address || !length)
 		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
 
-	_LOGD ("link: change %d: address: %s (%lu bytes)", ifindex,
-	       (mac = nm_utils_hwaddr_ntoa (address, length)),
-	       (unsigned long) length);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -5067,8 +5682,6 @@ link_set_name (NMPlatform *platform, int ifindex, const char *name)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	_LOGD ("link: change %d: name: %s", ifindex, name);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -5104,8 +5717,6 @@ link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	_LOGD ("link: change %d: mtu: %u", ifindex, (unsigned) mtu);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -5123,16 +5734,18 @@ nla_put_failure:
 }
 
 static gboolean
-link_set_sriov_num_vfs (NMPlatform *platform, int ifindex, guint num_vfs)
+link_set_sriov_params (NMPlatform *platform,
+                       int ifindex,
+                       guint num_vfs,
+                       int autoprobe)
 {
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	nm_auto_close int dirfd = -1;
-	int total, current;
+	gboolean current_autoprobe;
+	guint total, current_num;
 	char ifname[IFNAMSIZ];
 	char buf[64];
 
-	_LOGD ("link: change %d: num VFs: %u", ifindex, num_vfs);
-
 	if (!nm_platform_netns_push (platform, &netns))
 		return FALSE;
 
@@ -5140,46 +5753,69 @@ link_set_sriov_num_vfs (NMPlatform *platform, int ifindex, guint num_vfs)
 	if (!dirfd)
 		return FALSE;
 
-	total = nm_platform_sysctl_get_int32 (platform,
-	                                      NMP_SYSCTL_PATHID_NETDIR (dirfd,
-	                                                                ifname,
-	                                                                "device/sriov_totalvfs"),
-	                                      -1);
-	if (total < 1)
+	total = nm_platform_sysctl_get_int_checked (platform,
+	                                            NMP_SYSCTL_PATHID_NETDIR (dirfd,
+	                                                                      ifname,
+	                                                                      "device/sriov_totalvfs"),
+	                                            10, 0, G_MAXUINT, 0);
+	if (errno)
 		return FALSE;
 	if (num_vfs > total) {
 		_LOGW ("link: %d only supports %u VFs (requested %u)", ifindex, total, num_vfs);
 		num_vfs = total;
 	}
 
-	current = nm_platform_sysctl_get_int32 (platform,
-	                                        NMP_SYSCTL_PATHID_NETDIR (dirfd,
-	                                                                  ifname,
-	                                                                  "device/sriov_numvfs"),
-	                                        -1);
-	if (current == num_vfs)
+	/*
+	 * Take special care when setting new values:
+	 *  - don't touch anything if the right values are already set
+	 *  - to change the number of VFs or autoprobe we need to destroy existing VFs
+	 *  - the autoprobe setting is irrelevant when numvfs is zero
+	 */
+	current_num = nm_platform_sysctl_get_int_checked (platform,
+	                                                  NMP_SYSCTL_PATHID_NETDIR (dirfd,
+	                                                                            ifname,
+	                                                                            "device/sriov_numvfs"),
+	                                                  10, 0, G_MAXUINT, 0);
+	current_autoprobe = nm_platform_sysctl_get_int_checked (platform,
+	                                                        NMP_SYSCTL_PATHID_NETDIR (dirfd,
+	                                                                                  ifname,
+	                                                                                  "device/sriov_drivers_autoprobe"),
+	                                                        10, 0, G_MAXUINT, 0);
+	if (   current_num == num_vfs
+	    && (autoprobe == -1 || current_autoprobe == autoprobe))
 		return TRUE;
 
-	if (current != 0) {
-		/* We need to destroy all other VFs before changing the value */
+	if (current_num != 0) {
+		/* We need to destroy all other VFs before changing any value */
 		if (!nm_platform_sysctl_set (NM_PLATFORM_GET,
 		                             NMP_SYSCTL_PATHID_NETDIR (dirfd,
 		                                                       ifname,
 		                                                      "device/sriov_numvfs"),
 		                             "0")) {
-			_LOGW ("link: couldn't set SR-IOV num_vfs to %d: %s", 0, strerror (errno));
+			_LOGW ("link: couldn't reset SR-IOV num_vfs: %s", strerror (errno));
 			return FALSE;
 		}
-		if (num_vfs == 0)
-			return TRUE;
 	}
 
-	/* Finally, set the desired value */
+	if (num_vfs == 0)
+		return TRUE;
+
+	if (   autoprobe >= 0
+	    && current_autoprobe != autoprobe
+	    && !nm_platform_sysctl_set (NM_PLATFORM_GET,
+	                                NMP_SYSCTL_PATHID_NETDIR (dirfd,
+	                                                          ifname,
+	                                                          "device/sriov_drivers_autoprobe"),
+	                                nm_sprintf_buf (buf, "%d", autoprobe))) {
+		_LOGW ("link: couldn't set SR-IOV drivers-autoprobe to %d: %s", autoprobe, strerror (errno));
+		return FALSE;
+	}
+
 	if (!nm_platform_sysctl_set (NM_PLATFORM_GET,
 	                             NMP_SYSCTL_PATHID_NETDIR (dirfd,
 	                                                       ifname,
 	                                                       "device/sriov_numvfs"),
-	                             nm_sprintf_buf (buf, "%d", num_vfs))) {
+	                             nm_sprintf_buf (buf, "%u", num_vfs))) {
 		_LOGW ("link: couldn't set SR-IOV num_vfs to %d: %s", num_vfs, strerror (errno));
 		return FALSE;
 	}
@@ -5187,6 +5823,101 @@ link_set_sriov_num_vfs (NMPlatform *platform, int ifindex, guint num_vfs)
 	return TRUE;
 }
 
+static gboolean
+link_set_sriov_vfs (NMPlatform *platform, int ifindex, const NMPlatformVF *const *vfs)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *list, *info, *vlan_list;
+	guint i;
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+
+	if (!(list = nla_nest_start (nlmsg, IFLA_VFINFO_LIST)))
+		goto nla_put_failure;
+
+	for (i = 0; vfs[i]; i++) {
+		const NMPlatformVF *vf = vfs[i];
+
+		if (!(info = nla_nest_start (nlmsg, IFLA_VF_INFO)))
+			goto nla_put_failure;
+
+		if (vf->spoofchk >= 0) {
+			struct _ifla_vf_setting ivs = { 0 };
+
+			ivs.vf = vf->index;
+			ivs.setting = vf->spoofchk;
+			NLA_PUT (nlmsg, IFLA_VF_SPOOFCHK, sizeof (ivs), &ivs);
+		}
+
+		if (vf->trust >= 0) {
+			struct _ifla_vf_setting ivs = { 0 };
+
+			ivs.vf = vf->index;
+			ivs.setting = vf->trust;
+			NLA_PUT (nlmsg, IFLA_VF_TRUST, sizeof (ivs), &ivs);
+		}
+
+		if (vf->mac.len) {
+			struct ifla_vf_mac ivm = { 0 };
+
+			ivm.vf = vf->index;
+			memcpy (ivm.mac, vf->mac.data, vf->mac.len);
+			NLA_PUT (nlmsg, IFLA_VF_MAC, sizeof (ivm), &ivm);
+		}
+
+		if (vf->min_tx_rate || vf->max_tx_rate) {
+			struct _ifla_vf_rate ivr = { 0 };
+
+			ivr.vf = vf->index;
+			ivr.min_tx_rate = vf->min_tx_rate;
+			ivr.max_tx_rate = vf->max_tx_rate;
+			NLA_PUT (nlmsg, IFLA_VF_RATE, sizeof (ivr), &ivr);
+		}
+
+		/* Kernel only supports one VLAN per VF now. If this
+		 * changes in the future, we need to figure out how to
+		 * clear existing VLANs and set new ones in one message
+		 * with the new API.*/
+		if (vf->num_vlans > 1) {
+			_LOGW ("multiple VLANs per VF are not supported at the moment");
+			return FALSE;
+		} else {
+			struct _ifla_vf_vlan_info ivvi = { 0 };
+
+			if (!(vlan_list = nla_nest_start (nlmsg, IFLA_VF_VLAN_LIST)))
+				goto nla_put_failure;
+
+			ivvi.vf = vf->index;
+			if (vf->num_vlans == 1) {
+				ivvi.vlan = vf->vlans[0].id;
+				ivvi.qos = vf->vlans[0].qos;
+				ivvi.vlan_proto = htons (vf->vlans[0].proto_ad ? ETH_P_8021AD : ETH_P_8021Q);
+			} else {
+				/* Clear existing VLAN */
+				ivvi.vlan = 0;
+				ivvi.qos = 0;
+				ivvi.vlan_proto = htons (ETH_P_8021Q);
+			}
+
+			NLA_PUT (nlmsg, IFLA_VF_VLAN_INFO, sizeof (ivvi), &ivvi);
+			nla_nest_end (nlmsg, vlan_list);
+		}
+		nla_nest_end (nlmsg, info);
+	}
+	nla_nest_end (nlmsg, list);
+
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
+}
+
 static char *
 link_get_physical_port_id (NMPlatform *platform, int ifindex)
 {
@@ -5213,7 +5944,7 @@ link_get_dev_id (NMPlatform *platform, int ifindex)
 	                                           16, 0, G_MAXUINT16, 0);
 }
 
-static int
+static gboolean
 vlan_add (NMPlatform *platform,
           const char *name,
           int parent,
@@ -5229,10 +5960,6 @@ vlan_add (NMPlatform *platform,
 	G_STATIC_ASSERT (NM_VLAN_FLAG_MVRP == (guint32) VLAN_FLAG_MVRP);
 
 	vlan_flags &= (guint32) NM_VLAN_FLAGS_ALL;
-
-	_LOGD ("link: add vlan '%s', parent %d, vlan id %d, flags %X",
-	       name, parent, vlan_id, (unsigned) vlan_flags);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
@@ -5259,7 +5986,7 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static int
+static gboolean
 link_gre_add (NMPlatform *platform,
               const char *name,
               const NMPlatformLnkGre *props,
@@ -5268,14 +5995,6 @@ link_gre_add (NMPlatform *platform,
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	struct nlattr *info;
 	struct nlattr *data;
-	char buffer[INET_ADDRSTRLEN];
-
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "gre",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet4_ntop (props->local, NULL),
-	       nm_utils_inet4_ntop (props->remote, buffer));
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
@@ -5289,7 +6008,7 @@ link_gre_add (NMPlatform *platform,
 	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
 		goto nla_put_failure;
 
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "gre");
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, props->is_tap ? "gretap" : "gre");
 
 	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
 		goto nla_put_failure;
@@ -5303,18 +6022,20 @@ link_gre_add (NMPlatform *platform,
 	NLA_PUT_U8 (nlmsg, IFLA_GRE_PMTUDISC, !!props->path_mtu_discovery);
 	NLA_PUT_U32 (nlmsg, IFLA_GRE_IKEY, htonl (props->input_key));
 	NLA_PUT_U32 (nlmsg, IFLA_GRE_OKEY, htonl (props->output_key));
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_IFLAGS, htons (props->input_flags));
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_OFLAGS, htons (props->output_flags));
+	NLA_PUT_U16 (nlmsg, IFLA_GRE_IFLAGS, htons (props->input_flags));
+	NLA_PUT_U16 (nlmsg, IFLA_GRE_OFLAGS, htons (props->output_flags));
 
 	nla_nest_end (nlmsg, data);
 	nla_nest_end (nlmsg, info);
 
-	return do_add_link_with_lookup (platform, NM_LINK_TYPE_GRE, name, nlmsg, out_link);
+	return do_add_link_with_lookup (platform,
+	                                props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE,
+	                                name, nlmsg, out_link);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static int
+static gboolean
 link_ip6tnl_add (NMPlatform *platform,
                  const char *name,
                  const NMPlatformLnkIp6Tnl *props,
@@ -5323,15 +6044,9 @@ link_ip6tnl_add (NMPlatform *platform,
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	struct nlattr *info;
 	struct nlattr *data;
-	char buffer[INET_ADDRSTRLEN];
 	guint32 flowinfo;
 
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "ip6tnl",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet6_ntop (&props->local, NULL),
-	       nm_utils_inet6_ntop (&props->remote, buffer));
+	g_return_val_if_fail (!props->is_gre, FALSE);
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
@@ -5376,7 +6091,69 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static int
+static gboolean
+link_ip6gre_add (NMPlatform *platform,
+                 const char *name,
+                 const NMPlatformLnkIp6Tnl *props,
+                 const NMPlatformLink **out_link)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+	guint32 flowinfo;
+
+	g_return_val_if_fail (props->is_gre, FALSE);
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE | NLM_F_EXCL,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
+
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
+
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, props->is_tap ? "ip6gretap" : "ip6gre");
+
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
+
+	if (props->parent_ifindex)
+		NLA_PUT_U32 (nlmsg, IFLA_GRE_LINK, props->parent_ifindex);
+
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_IKEY, htonl (props->input_key));
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_OKEY, htonl (props->output_key));
+	NLA_PUT_U16 (nlmsg, IFLA_GRE_IFLAGS, htons (props->input_flags));
+	NLA_PUT_U16 (nlmsg, IFLA_GRE_OFLAGS, htons (props->output_flags));
+
+	if (memcmp (&props->local, &in6addr_any, sizeof (in6addr_any)))
+		NLA_PUT (nlmsg, IFLA_GRE_LOCAL, sizeof (props->local), &props->local);
+	if (memcmp (&props->remote, &in6addr_any, sizeof (in6addr_any)))
+		NLA_PUT (nlmsg, IFLA_GRE_REMOTE, sizeof (props->remote), &props->remote);
+
+	NLA_PUT_U8 (nlmsg, IFLA_GRE_TTL, props->ttl);
+	NLA_PUT_U8 (nlmsg, IFLA_GRE_ENCAP_LIMIT, props->encap_limit);
+
+	flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK;
+	flowinfo |=   (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT)
+	            & IP6_FLOWINFO_TCLASS_MASK;
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_FLOWINFO, htonl (flowinfo));
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_FLAGS, props->flags);
+
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
+
+	return do_add_link_with_lookup (platform,
+	                                props->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE,
+	                                name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
+}
+
+static gboolean
 link_ipip_add (NMPlatform *platform,
                const char *name,
                const NMPlatformLnkIpIp *props,
@@ -5385,14 +6162,6 @@ link_ipip_add (NMPlatform *platform,
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	struct nlattr *info;
 	struct nlattr *data;
-	char buffer[INET_ADDRSTRLEN];
-
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "ipip",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet4_ntop (props->local, NULL),
-	       nm_utils_inet4_ntop (props->remote, buffer));
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
@@ -5427,7 +6196,7 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static int
+static gboolean
 link_macsec_add (NMPlatform *platform,
                  const char *name,
                  int parent,
@@ -5438,11 +6207,6 @@ link_macsec_add (NMPlatform *platform,
 	struct nlattr *info;
 	struct nlattr *data;
 
-	_LOGD ("adding macsec '%s' parent %u sci %llx",
-	       name,
-	       parent,
-	       (unsigned long long) props->sci);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
@@ -5489,7 +6253,7 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static int
+static gboolean
 link_macvlan_add (NMPlatform *platform,
                   const char *name,
                   int parent,
@@ -5500,12 +6264,6 @@ link_macvlan_add (NMPlatform *platform,
 	struct nlattr *info;
 	struct nlattr *data;
 
-	_LOGD ("adding %s '%s' parent %u mode %u",
-	       props->tap ? "macvtap" : "macvlan",
-	       name,
-	       parent,
-	       props->mode);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
@@ -5538,7 +6296,7 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static int
+static gboolean
 link_sit_add (NMPlatform *platform,
               const char *name,
               const NMPlatformLnkSit *props,
@@ -5547,14 +6305,6 @@ link_sit_add (NMPlatform *platform,
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	struct nlattr *info;
 	struct nlattr *data;
-	char buffer[INET_ADDRSTRLEN];
-
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "sit",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet4_ntop (props->local, NULL),
-	       nm_utils_inet4_ntop (props->remote, buffer));
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
@@ -5658,9 +6408,6 @@ link_vxlan_add (NMPlatform *platform,
 
 	g_return_val_if_fail (props, FALSE);
 
-	_LOGD ("link: add vxlan '%s', parent %d, vxlan id %d",
-	       name, props->parent_ifindex, props->id);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
@@ -5718,6 +6465,41 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
+static gboolean
+link_6lowpan_add (NMPlatform *platform,
+                  const char *name,
+                  int parent,
+                  const NMPlatformLink **out_link)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE | NLM_F_EXCL,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
+
+	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
+
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
+
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "lowpan");
+
+	nla_nest_end (nlmsg, info);
+
+	return do_add_link_with_lookup (platform,
+	                                NM_LINK_TYPE_6LOWPAN,
+	                                name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
+}
+
+
 static void
 _vlan_change_vlan_qos_mapping_create (gboolean is_ingress_map,
                                       gboolean reset_all,
@@ -5812,9 +6594,6 @@ link_vlan_change (NMPlatform *platform,
 	guint new_n_egress_map = 0;
 	gs_free NMVlanQosMapping *new_ingress_map = NULL;
 	gs_free NMVlanQosMapping *new_egress_map = NULL;
-	char s_flags[64];
-	char s_ingress[256];
-	char s_egress[256];
 
 	obj_cache = nmp_cache_lookup_link (nm_platform_get_cache (platform), ifindex);
 	if (   !obj_cache
@@ -5845,26 +6624,6 @@ link_vlan_change (NMPlatform *platform,
 	                                      &new_egress_map,
 	                                      &new_n_egress_map);
 
-	_LOGD ("link: change %d: vlan:%s%s%s",
-	       ifindex,
-	       flags_mask
-	           ? nm_sprintf_buf (s_flags, " flags 0x%x/0x%x", (unsigned) flags_set, (unsigned) flags_mask)
-	           : "",
-	       new_n_ingress_map
-	           ? nm_platform_vlan_qos_mapping_to_string (" ingress-qos-map",
-	                                                     new_ingress_map,
-	                                                     new_n_ingress_map,
-	                                                     s_ingress,
-	                                                     sizeof (s_ingress))
-	           : "",
-	       new_n_egress_map
-	           ? nm_platform_vlan_qos_mapping_to_string (" egress-qos-map",
-	                                                     new_egress_map,
-	                                                     new_n_egress_map,
-	                                                     s_egress,
-	                                                     sizeof (s_egress))
-	           : "");
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -5891,8 +6650,6 @@ link_enslave (NMPlatform *platform, int master, int slave)
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	int ifindex = slave;
 
-	_LOGD ("link: change %d: enslave: master %d", slave, master);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -5980,43 +6737,26 @@ infiniband_partition_delete (NMPlatform *platform, int parent, int p_key)
 
 /*****************************************************************************/
 
-static WifiData *
-wifi_get_wifi_data (NMPlatform *platform, int ifindex)
+static GObject *
+get_ext_data (NMPlatform *platform, int ifindex)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	const NMPlatformLink *pllink;
-	WifiData *wifi_data;
-
-	wifi_data = g_hash_table_lookup (priv->wifi_data, GINT_TO_POINTER (ifindex));
-	pllink = nm_platform_link_get (platform, ifindex);
-
-	if (!wifi_data) {
-		if (pllink) {
-			if (pllink->type == NM_LINK_TYPE_WIFI)
-				wifi_data = wifi_utils_init (ifindex, TRUE);
-			else if (pllink->type == NM_LINK_TYPE_OLPC_MESH) {
-				/* The kernel driver now uses nl80211, but we force use of WEXT because
-				 * the cfg80211 interactions are not quite ready to support access to
-				 * mesh control through nl80211 just yet.
-				 */
-#if HAVE_WEXT
-				wifi_data = wifi_wext_init (ifindex, FALSE);
-#endif
-			}
+	const NMPObject *obj;
 
-			if (wifi_data)
-				g_hash_table_insert (priv->wifi_data, GINT_TO_POINTER (ifindex), wifi_data);
-		}
-	}
+	obj = nmp_cache_lookup_link (nm_platform_get_cache (platform), ifindex);
+	if (!obj)
+		return NULL;
 
-	return wifi_data;
+	return obj->_link.ext_data;
 }
+
+/*****************************************************************************/
+
 #define WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, retval) \
 	nm_auto_pop_netns NMPNetns *netns = NULL; \
-	WifiData *wifi_data; \
+	NMWifiUtils *wifi_data; \
 	if (!nm_platform_netns_push (platform, &netns)) \
 		return retval; \
-	wifi_data = wifi_get_wifi_data (platform, ifindex); \
+	wifi_data = NM_WIFI_UTILS (get_ext_data (platform, ifindex)); \
 	if (!wifi_data) \
 		return retval;
 
@@ -6025,7 +6765,7 @@ wifi_get_capabilities (NMPlatform *platform, int ifindex, NMDeviceWifiCapabiliti
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
 	if (caps)
-		*caps = wifi_utils_get_caps (wifi_data);
+		*caps = nm_wifi_utils_get_caps (wifi_data);
 	return TRUE;
 }
 
@@ -6033,63 +6773,70 @@ static gboolean
 wifi_get_bssid (NMPlatform *platform, int ifindex, guint8 *bssid)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
-	return wifi_utils_get_bssid (wifi_data, bssid);
+	return nm_wifi_utils_get_bssid (wifi_data, bssid);
 }
 
 static guint32
 wifi_get_frequency (NMPlatform *platform, int ifindex)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, 0);
-	return wifi_utils_get_freq (wifi_data);
+	return nm_wifi_utils_get_freq (wifi_data);
 }
 
 static gboolean
 wifi_get_quality (NMPlatform *platform, int ifindex)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
-	return wifi_utils_get_qual (wifi_data);
+	return nm_wifi_utils_get_qual (wifi_data);
 }
 
 static guint32
 wifi_get_rate (NMPlatform *platform, int ifindex)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
-	return wifi_utils_get_rate (wifi_data);
+	return nm_wifi_utils_get_rate (wifi_data);
 }
 
 static NM80211Mode
 wifi_get_mode (NMPlatform *platform, int ifindex)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, NM_802_11_MODE_UNKNOWN);
-	return wifi_utils_get_mode (wifi_data);
+	return nm_wifi_utils_get_mode (wifi_data);
 }
 
 static void
 wifi_set_mode (NMPlatform *platform, int ifindex, NM80211Mode mode)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, );
-	wifi_utils_set_mode (wifi_data, mode);
+	nm_wifi_utils_set_mode (wifi_data, mode);
 }
 
 static void
 wifi_set_powersave (NMPlatform *platform, int ifindex, guint32 powersave)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, );
-	wifi_utils_set_powersave (wifi_data, powersave);
+	nm_wifi_utils_set_powersave (wifi_data, powersave);
 }
 
 static guint32
 wifi_find_frequency (NMPlatform *platform, int ifindex, const guint32 *freqs)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, 0);
-	return wifi_utils_find_freq (wifi_data, freqs);
+	return nm_wifi_utils_find_freq (wifi_data, freqs);
 }
 
 static void
 wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean running)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, );
-	wifi_utils_indicate_addressing_running (wifi_data, running);
+	nm_wifi_utils_indicate_addressing_running (wifi_data, running);
+}
+
+static NMSettingWirelessWakeOnWLan
+wifi_get_wake_on_wlan (NMPlatform *platform, int ifindex)
+{
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
+	return nm_wifi_utils_get_wake_on_wlan (wifi_data);
 }
 
 static gboolean
@@ -6097,7 +6844,7 @@ wifi_set_wake_on_wlan (NMPlatform *platform, int ifindex,
                        NMSettingWirelessWakeOnWLan wowl)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
-	return wifi_utils_set_wake_on_wlan (wifi_data, wowl);
+	return nm_wifi_utils_set_wake_on_wlan (wifi_data, wowl);
 }
 
 /*****************************************************************************/
@@ -6148,21 +6895,56 @@ static guint32
 mesh_get_channel (NMPlatform *platform, int ifindex)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, 0);
-	return wifi_utils_get_mesh_channel (wifi_data);
+	return nm_wifi_utils_get_mesh_channel (wifi_data);
 }
 
 static gboolean
 mesh_set_channel (NMPlatform *platform, int ifindex, guint32 channel)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
-	return wifi_utils_set_mesh_channel (wifi_data, channel);
+	return nm_wifi_utils_set_mesh_channel (wifi_data, channel);
 }
 
 static gboolean
 mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
-	return wifi_utils_set_mesh_ssid (wifi_data, ssid, len);
+	return nm_wifi_utils_set_mesh_ssid (wifi_data, ssid, len);
+}
+
+/*****************************************************************************/
+
+#define WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, retval) \
+	NMWpanUtils *wpan_data = NM_WPAN_UTILS (get_ext_data (platform, ifindex)); \
+	if (!wpan_data) \
+		return retval;
+
+static guint16
+wpan_get_pan_id (NMPlatform *platform, int ifindex)
+{
+	WPAN_GET_WPAN_DATA (wpan_data, platform, ifindex, G_MAXINT16);
+	return nm_wpan_utils_get_pan_id (wpan_data);
+}
+
+static gboolean
+wpan_set_pan_id (NMPlatform *platform, int ifindex, guint16 pan_id)
+{
+	WPAN_GET_WPAN_DATA (wpan_data, platform, ifindex, FALSE);
+	return nm_wpan_utils_set_pan_id (wpan_data, pan_id);
+}
+
+static guint16
+wpan_get_short_addr (NMPlatform *platform, int ifindex)
+{
+	WPAN_GET_WPAN_DATA (wpan_data, platform, ifindex, G_MAXINT16);
+	return nm_wpan_utils_get_short_addr (wpan_data);
+}
+
+static gboolean
+wpan_set_short_addr (NMPlatform *platform, int ifindex, guint16 short_addr)
+{
+	WPAN_GET_WPAN_DATA (wpan_data, platform, ifindex, FALSE);
+	return nm_wpan_utils_set_short_addr (wpan_data, short_addr);
 }
 
 /*****************************************************************************/
@@ -6179,12 +6961,12 @@ link_get_wake_on_lan (NMPlatform *platform, int ifindex)
 	if (type == NM_LINK_TYPE_ETHERNET)
 		return nmp_utils_ethtool_get_wake_on_lan (ifindex);
 	else if (type == NM_LINK_TYPE_WIFI) {
-		WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
+		NMWifiUtils *wifi_data = NM_WIFI_UTILS (get_ext_data (platform, ifindex));
 
 		if (!wifi_data)
 			return FALSE;
 
-		return wifi_utils_get_wowlan (wifi_data);
+		return nm_wifi_utils_get_wake_on_wlan (wifi_data) != NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE;
 	} else
 		return FALSE;
 }
@@ -6998,6 +7780,14 @@ handle_udev_event (NMUdevClient *udev_client,
 
 /*****************************************************************************/
 
+void
+nm_linux_platform_setup (void)
+{
+	nm_platform_setup (nm_linux_platform_new (FALSE, FALSE));
+}
+
+/*****************************************************************************/
+
 static void
 nm_linux_platform_init (NMLinuxPlatform *self)
 {
@@ -7006,7 +7796,6 @@ nm_linux_platform_init (NMLinuxPlatform *self)
 	priv->delayed_action.list_master_connected = g_ptr_array_new ();
 	priv->delayed_action.list_refresh_link = g_ptr_array_new ();
 	priv->delayed_action.list_wait_for_nl_response = g_array_new (FALSE, TRUE, sizeof (DelayedActionWaitForNlResponseData));
-	priv->wifi_data = g_hash_table_new_full (nm_direct_hash, NULL, NULL, (GDestroyNotify) wifi_utils_unref);
 }
 
 static void
@@ -7036,6 +7825,18 @@ constructed (GObject *_object)
 	                                   nmp_netns_get_current () == nmp_netns_get_initial () ? "/main" : "")),
 	       nm_platform_get_use_udev (platform) ? "use" : "no");
 
+
+	priv->genl = nl_socket_alloc ();
+	g_assert (priv->genl);
+
+	nle = nl_connect (priv->genl, NETLINK_GENERIC);
+	if (nle) {
+		_LOGE ("unable to connect the generic netlink socket \"%s\" (%d)",
+		       nl_geterror (nle), -nle);
+		nl_socket_free (priv->genl);
+		priv->genl = NULL;
+	}
+
 	priv->nlh = nl_socket_alloc ();
 	g_assert (priv->nlh);
 
@@ -7127,6 +7928,22 @@ constructed (GObject *_object)
 	}
 }
 
+NMPlatform *
+nm_linux_platform_new (gboolean log_with_ptr, gboolean netns_support)
+{
+	gboolean use_udev = FALSE;
+
+	if (   nmp_netns_is_initial ()
+	    && access ("/sys", W_OK) == 0)
+		use_udev = TRUE;
+
+	return g_object_new (NM_TYPE_LINUX_PLATFORM,
+	                     NM_PLATFORM_LOG_WITH_PTR, log_with_ptr,
+	                     NM_PLATFORM_USE_UDEV, use_udev,
+	                     NM_PLATFORM_NETNS_SUPPORT, netns_support,
+	                     NULL);
+}
+
 static void
 dispose (GObject *object)
 {
@@ -7154,12 +7971,12 @@ finalize (GObject *object)
 	g_ptr_array_unref (priv->delayed_action.list_refresh_link);
 	g_array_unref (priv->delayed_action.list_wait_for_nl_response);
 
+	nl_socket_free (priv->genl);
+
 	g_source_remove (priv->event_id);
 	g_io_channel_unref (priv->event_channel);
 	nl_socket_free (priv->nlh);
 
-	g_hash_table_unref (priv->wifi_data);
-
 	if (priv->sysctl_get_prev_values) {
 		sysctl_clear_cache_list = g_slist_remove (sysctl_clear_cache_list, object);
 		g_hash_table_destroy (priv->sysctl_get_prev_values);
@@ -7205,7 +8022,8 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_get_permanent_address = link_get_permanent_address;
 	platform_class->link_set_mtu = link_set_mtu;
 	platform_class->link_set_name = link_set_name;
-	platform_class->link_set_sriov_num_vfs = link_set_sriov_num_vfs;
+	platform_class->link_set_sriov_params = link_set_sriov_params;
+	platform_class->link_set_sriov_vfs = link_set_sriov_vfs;
 
 	platform_class->link_get_physical_port_id = link_get_physical_port_id;
 	platform_class->link_get_dev_id = link_get_dev_id;
@@ -7238,19 +8056,27 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->wifi_set_powersave = wifi_set_powersave;
 	platform_class->wifi_find_frequency = wifi_find_frequency;
 	platform_class->wifi_indicate_addressing_running = wifi_indicate_addressing_running;
+	platform_class->wifi_get_wake_on_wlan = wifi_get_wake_on_wlan;
 	platform_class->wifi_set_wake_on_wlan = wifi_set_wake_on_wlan;
 
 	platform_class->mesh_get_channel = mesh_get_channel;
 	platform_class->mesh_set_channel = mesh_set_channel;
 	platform_class->mesh_set_ssid = mesh_set_ssid;
 
+	platform_class->wpan_get_pan_id = wpan_get_pan_id;
+	platform_class->wpan_set_pan_id = wpan_set_pan_id;
+	platform_class->wpan_get_short_addr = wpan_get_short_addr;
+	platform_class->wpan_set_short_addr = wpan_set_short_addr;
+
 	platform_class->link_gre_add = link_gre_add;
 	platform_class->link_ip6tnl_add = link_ip6tnl_add;
+	platform_class->link_ip6gre_add = link_ip6gre_add;
 	platform_class->link_macsec_add = link_macsec_add;
 	platform_class->link_macvlan_add = link_macvlan_add;
 	platform_class->link_ipip_add = link_ipip_add;
 	platform_class->link_sit_add = link_sit_add;
 	platform_class->link_tun_add = link_tun_add;
+	platform_class->link_6lowpan_add = link_6lowpan_add;
 
 	platform_class->object_delete = object_delete;
 	platform_class->ip4_address_add = ip4_address_add;
diff --git a/src/platform/nm-netlink.c b/src/platform/nm-netlink.c
index 4cb19780..3e2ad911 100644
--- a/src/platform/nm-netlink.c
+++ b/src/platform/nm-netlink.c
@@ -52,7 +52,6 @@ struct nl_msg {
 	struct ucred            nm_creds;
 	struct nlmsghdr *       nm_nlh;
 	size_t                  nm_size;
-	int                     nm_refcnt;
 };
 
 struct nl_sock {
@@ -84,18 +83,18 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_geterror, int,
 )
 
 const char *
-nl_geterror (int err)
+nl_geterror (int nlerr)
 {
 	const char *s;
 
-	err = nl_errno (err);
+	nlerr = nl_errno (nlerr);
 
-	if (err >= _NLE_BASE) {
-		s = _geterror (err);
+	if (nlerr >= _NLE_BASE) {
+		s = _geterror (nlerr);
 		if (s)
 			return s;
 	}
-	return g_strerror (err);
+	return g_strerror (nlerr);
 }
 
 /*****************************************************************************/
@@ -252,27 +251,13 @@ nlmsg_reserve (struct nl_msg *n, size_t len, int pad)
 	n->nm_nlh->nlmsg_len += tlen;
 
 	if (tlen > len)
-		memset(buf + len, 0, tlen - len);
+		memset (buf + len, 0, tlen - len);
 
 	return buf;
 }
 
 /*****************************************************************************/
 
-static int
- get_default_page_size (void)
-{
-	static int val = 0;
-	int v;
-
-	if (G_UNLIKELY (val == 0)) {
-		v = getpagesize ();
-		g_assert (v > 0);
-		val = v;
-	}
-	return val;
-}
-
 struct nlattr *
 nla_reserve (struct nl_msg *msg, int attrtype, int attrlen)
 {
@@ -282,22 +267,38 @@ nla_reserve (struct nl_msg *msg, int attrtype, int attrlen)
 	if (attrlen < 0)
 		return NULL;
 
-	tlen = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) + nla_total_size(attrlen);
+	tlen = NLMSG_ALIGN (msg->nm_nlh->nlmsg_len) + nla_total_size (attrlen);
 
 	if (tlen > msg->nm_size)
 		return NULL;
 
-	nla = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
+	nla = (struct nlattr *) nlmsg_tail (msg->nm_nlh);
 	nla->nla_type = attrtype;
-	nla->nla_len = nla_attr_size(attrlen);
+	nla->nla_len = nla_attr_size (attrlen);
 
 	if (attrlen)
-		memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen));
+		memset ((unsigned char *) nla + nla->nla_len, 0, nla_padlen (attrlen));
 	msg->nm_nlh->nlmsg_len = tlen;
 
 	return nla;
 }
 
+/*****************************************************************************/
+
+static int
+get_default_page_size (void)
+{
+	static int val = 0;
+	int v;
+
+	if (G_UNLIKELY (val == 0)) {
+		v = getpagesize ();
+		g_assert (v > 0);
+		val = v;
+	}
+	return val;
+}
+
 struct nl_msg *
 nlmsg_alloc_size (size_t len)
 {
@@ -308,7 +309,6 @@ nlmsg_alloc_size (size_t len)
 
 	nm = g_slice_new0 (struct nl_msg);
 
-	nm->nm_refcnt = 1;
 	nm->nm_protocol = -1;
 	nm->nm_size = len;
 	nm->nm_nlh = g_malloc0 (len);
@@ -331,48 +331,40 @@ nlmsg_alloc (void)
 	return nlmsg_alloc_size (get_default_page_size ());
 }
 
-/**
- * Allocate a new netlink message with maximum payload size specified.
- */
 struct nl_msg *
-nlmsg_alloc_inherit (struct nlmsghdr *hdr)
+nlmsg_alloc_convert (struct nlmsghdr *hdr)
 {
 	struct nl_msg *nm;
 
-	nm = nlmsg_alloc ();
-	if (hdr) {
-		struct nlmsghdr *new = nm->nm_nlh;
-
-		new->nlmsg_type = hdr->nlmsg_type;
-		new->nlmsg_flags = hdr->nlmsg_flags;
-		new->nlmsg_seq = hdr->nlmsg_seq;
-		new->nlmsg_pid = hdr->nlmsg_pid;
-	}
-
+	nm = nlmsg_alloc_size (NLMSG_ALIGN (hdr->nlmsg_len));
+	memcpy (nm->nm_nlh, hdr, hdr->nlmsg_len);
 	return nm;
 }
 
 struct nl_msg *
-nlmsg_alloc_convert (struct nlmsghdr *hdr)
+nlmsg_alloc_simple (int nlmsgtype, int flags)
 {
 	struct nl_msg *nm;
+	struct nlmsghdr *new;
 
-	nm = nlmsg_alloc_size (NLMSG_ALIGN (hdr->nlmsg_len));
-	memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len);
+	nm = nlmsg_alloc ();
+	new = nm->nm_nlh;
+	new->nlmsg_type = nlmsgtype;
+	new->nlmsg_flags = flags;
 	return nm;
 }
 
-struct nl_msg *
-nlmsg_alloc_simple (int nlmsgtype, int flags)
+void nlmsg_free (struct nl_msg *msg)
 {
-	struct nlmsghdr nlh = {
-		.nlmsg_type = nlmsgtype,
-		.nlmsg_flags = flags,
-	};
+	if (!msg)
+		return;
 
-	return nlmsg_alloc_inherit (&nlh);
+	g_free (msg->nm_nlh);
+	g_slice_free (struct nl_msg, msg);
 }
 
+/*****************************************************************************/
+
 int
 nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad)
 {
@@ -382,19 +374,21 @@ nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad)
 	if (tmp == NULL)
 		return -ENOMEM;
 
-	memcpy(tmp, data, len);
+	memcpy (tmp, data, len);
 	return 0;
 }
 
+/*****************************************************************************/
+
 int
 nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
              int maxtype, const struct nla_policy *policy)
 {
-	if (!nlmsg_valid_hdr(nlh, hdrlen))
+	if (!nlmsg_valid_hdr (nlh, hdrlen))
 		return -NLE_MSG_TOOSHORT;
 
-	return nla_parse (tb, maxtype, nlmsg_attrdata(nlh, hdrlen),
-	                  nlmsg_attrlen(nlh, hdrlen), policy);
+	return nla_parse (tb, maxtype, nlmsg_attrdata (nlh, hdrlen),
+	                  nlmsg_attrlen (nlh, hdrlen), policy);
 }
 
 struct nlmsghdr *
@@ -412,8 +406,8 @@ nlmsg_put (struct nl_msg *n, uint32_t pid, uint32_t seq,
 	nlh->nlmsg_pid = pid;
 	nlh->nlmsg_seq = seq;
 
-	if (payload > 0 &&
-	    nlmsg_reserve(n, payload, NLMSG_ALIGNTO) == NULL)
+	if (   payload > 0
+	    && nlmsg_reserve (n, payload, NLMSG_ALIGNTO) == NULL)
 		return NULL;
 
 	return nlh;
@@ -424,8 +418,8 @@ nla_get_u64 (const struct nlattr *nla)
 {
 	uint64_t tmp = 0;
 
-	if (nla && nla_len(nla) >= sizeof (tmp))
-		memcpy(&tmp, nla_data(nla), sizeof (tmp));
+	if (nla && nla_len (nla) >= sizeof (tmp))
+		memcpy (&tmp, nla_data (nla), sizeof (tmp));
 
 	return tmp;
 }
@@ -433,8 +427,8 @@ nla_get_u64 (const struct nlattr *nla)
 size_t
 nla_strlcpy (char *dst, const struct nlattr *nla, size_t dstsize)
 {
-	size_t srclen = nla_len(nla);
-	const char *src = nla_data(nla);
+	size_t srclen = nla_len (nla);
+	const char *src = nla_data (nla);
 
 	if (srclen > 0 && src[srclen - 1] == '\0')
 		srclen--;
@@ -442,8 +436,8 @@ nla_strlcpy (char *dst, const struct nlattr *nla, size_t dstsize)
 	if (dstsize > 0) {
 		size_t len = (srclen >= dstsize) ? dstsize - 1 : srclen;
 
-		memset(dst, 0, dstsize);
-		memcpy(dst, src, len);
+		memset (dst, 0, dstsize);
+		memcpy (dst, src, len);
 	}
 
 	return srclen;
@@ -458,7 +452,7 @@ nla_memcpy (void *dest, const struct nlattr *src, int count)
 		return 0;
 
 	minlen = NM_MIN (count, (int) nla_len (src));
-	memcpy(dest, nla_data(src), minlen);
+	memcpy (dest, nla_data (src), minlen);
 
 	return minlen;
 }
@@ -468,7 +462,7 @@ nla_put (struct nl_msg *msg, int attrtype, int datalen, const void *data)
 {
 	struct nlattr *nla;
 
-	nla = nla_reserve(msg, attrtype, datalen);
+	nla = nla_reserve (msg, attrtype, datalen);
 	if (!nla) {
 		if (datalen < 0)
 			g_return_val_if_reached (-NLE_BUG);
@@ -477,7 +471,7 @@ nla_put (struct nl_msg *msg, int attrtype, int datalen, const void *data)
 	}
 
 	if (datalen > 0)
-		memcpy (nla_data(nla), data, datalen);
+		memcpy (nla_data (nla), data, datalen);
 
 	return 0;
 }
@@ -501,21 +495,21 @@ nla_nest_cancel (struct nl_msg *msg, const struct nlattr *attr)
 {
 	ssize_t len;
 
-	len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) attr;
+	len = (char *) nlmsg_tail (msg->nm_nlh) - (char *) attr;
 	if (len < 0)
 		g_return_if_reached ();
 	else if (len > 0) {
 		msg->nm_nlh->nlmsg_len -= len;
-		memset(nlmsg_tail(msg->nm_nlh), 0, len);
+		memset (nlmsg_tail (msg->nm_nlh), 0, len);
 	}
 }
 
 struct nlattr *
 nla_nest_start (struct nl_msg *msg, int attrtype)
 {
-	struct nlattr *start = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
+	struct nlattr *start = (struct nlattr *) nlmsg_tail (msg->nm_nlh);
 
-	if (nla_put(msg, attrtype, 0, NULL) < 0)
+	if (nla_put (msg, attrtype, 0, NULL) < 0)
 		return NULL;
 
 	return start;
@@ -526,7 +520,7 @@ _nest_end (struct nl_msg *msg, struct nlattr *start, int keep_empty)
 {
 	size_t pad, len;
 
-	len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) start;
+	len = (char *) nlmsg_tail (msg->nm_nlh) - (char *) start;
 
 	if (   len > USHRT_MAX
 	    || (!keep_empty && len == NLA_HDRLEN)) {
@@ -534,7 +528,7 @@ _nest_end (struct nl_msg *msg, struct nlattr *start, int keep_empty)
 		 * Max nlattr size exceeded or empty nested attribute, trim the
 		 * attribute header again
 		 */
-		nla_nest_cancel(msg, start);
+		nla_nest_cancel (msg, start);
 
 		/* Return error only if nlattr size was exceeded */
 		return (len == NLA_HDRLEN) ? 0 : -NLE_ATTRSIZE;
@@ -542,7 +536,7 @@ _nest_end (struct nl_msg *msg, struct nlattr *start, int keep_empty)
 
 	start->nla_len = len;
 
-	pad = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) - msg->nm_nlh->nlmsg_len;
+	pad = NLMSG_ALIGN (msg->nm_nlh->nlmsg_len) - msg->nm_nlh->nlmsg_len;
 	if (pad > 0) {
 		/*
 		 * Data inside attribute does not end at a alignment boundry.
@@ -550,7 +544,7 @@ _nest_end (struct nl_msg *msg, struct nlattr *start, int keep_empty)
 		 * the message. nlmsg_reserve() may never fail in this situation,
 		 * the allocate message buffer must be a multiple of NLMSG_ALIGNTO.
 		 */
-		if (!nlmsg_reserve(msg, pad, 0))
+		if (!nlmsg_reserve (msg, pad, 0))
 			g_return_val_if_reached (-NLE_BUG);
 	}
 
@@ -578,7 +572,7 @@ validate_nla (const struct nlattr *nla, int maxtype,
 {
 	const struct nla_policy *pt;
 	unsigned int minlen = 0;
-	int type = nla_type(nla);
+	int type = nla_type (nla);
 
 	if (type < 0 || type > maxtype)
 		return 0;
@@ -593,15 +587,15 @@ validate_nla (const struct nlattr *nla, int maxtype,
 	else if (pt->type != NLA_UNSPEC)
 		minlen = nla_attr_minlen[pt->type];
 
-	if (nla_len(nla) < minlen)
+	if (nla_len (nla) < minlen)
 		return -NLE_UNSPEC;
 
-	if (pt->maxlen && nla_len(nla) > pt->maxlen)
+	if (pt->maxlen && nla_len (nla) > pt->maxlen)
 		return -NLE_UNSPEC;
 
 	if (pt->type == NLA_STRING) {
-		const char *data = nla_data(nla);
-		if (data[nla_len(nla) - 1] != '\0')
+		const char *data = nla_data (nla);
+		if (data[nla_len (nla) - 1] != '\0')
 			return -NLE_UNSPEC;
 	}
 
@@ -613,48 +607,32 @@ nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
            const struct nla_policy *policy)
 {
 	struct nlattr *nla;
-	int rem, err;
+	int rem, nlerr;
 
-	memset(tb, 0, sizeof (struct nlattr *) * (maxtype + 1));
+	memset (tb, 0, sizeof (struct nlattr *) * (maxtype + 1));
 
-	nla_for_each_attr(nla, head, len, rem) {
-		int type = nla_type(nla);
+	nla_for_each_attr (nla, head, len, rem) {
+		int type = nla_type (nla);
 
 		if (type > maxtype)
 			continue;
 
 		if (policy) {
-			err = validate_nla(nla, maxtype, policy);
-			if (err < 0)
+			nlerr = validate_nla (nla, maxtype, policy);
+			if (nlerr < 0)
 				goto errout;
 		}
 
 		tb[type] = nla;
 	}
 
-	err = 0;
+	nlerr = 0;
 errout:
-	return err;
+	return nlerr;
 }
 
 /*****************************************************************************/
 
-void nlmsg_free (struct nl_msg *msg)
-{
-	if (!msg)
-		return;
-
-	if (msg->nm_refcnt < 1)
-		g_return_if_reached ();
-
-	msg->nm_refcnt--;
-
-	if (msg->nm_refcnt <= 0) {
-		g_free (msg->nm_nlh);
-		g_slice_free (struct nl_msg, msg);
-	}
-}
-
 int
 nlmsg_get_proto (struct nl_msg *msg)
 {
@@ -813,7 +791,7 @@ int
 genl_ctrl_resolve (struct nl_sock *sk, const char *name)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
-	int result = -ENOMEM;
+	int nlerr;
 	gint32 response_data = -1;
 	const struct nl_cb cb = {
 		.valid_cb = _genl_parse_getfamily,
@@ -824,31 +802,29 @@ genl_ctrl_resolve (struct nl_sock *sk, const char *name)
 
 	if (!genlmsg_put (msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL,
 	                  0, 0, CTRL_CMD_GETFAMILY, 1))
-		goto out;
+		return -ENOMEM;
 
-	if (nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name) < 0)
-		goto out;
+	nlerr = nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name);
+	if (nlerr < 0)
+		return nlerr;
 
-	result = nl_send_auto (sk, msg);
-	if (result < 0)
-		goto out;
+	nlerr = nl_send_auto (sk, msg);
+	if (nlerr < 0)
+		return nlerr;
 
-	result = nl_recvmsgs (sk, &cb);
-	if (result < 0)
-		goto out;
+	nlerr = nl_recvmsgs (sk, &cb);
+	if (nlerr < 0)
+		return nlerr;
 
 	/* If search was successful, request may be ACKed after data */
-	result = nl_wait_for_ack (sk, NULL);
-	if (result < 0)
-		goto out;
+	nlerr = nl_wait_for_ack (sk, NULL);
+	if (nlerr < 0)
+		return nlerr;
 
-	if (response_data > 0)
-		result = response_data;
-	else
-		result = -ENOENT;
+	if (response_data < 0)
+		return -NLE_UNSPEC;
 
-out:
-	return result;
+	return response_data;
 }
 
 /*****************************************************************************/
@@ -863,7 +839,7 @@ nl_socket_alloc (void)
 	sk->s_fd = -1;
 	sk->s_local.nl_family = AF_NETLINK;
 	sk->s_peer.nl_family = AF_NETLINK;
-	sk->s_seq_expect = sk->s_seq_next = time(NULL);
+	sk->s_seq_expect = sk->s_seq_next = time (NULL);
 
 	return sk;
 }
@@ -938,7 +914,7 @@ nl_socket_set_nonblocking (const struct nl_sock *sk)
 	if (sk->s_fd == -1)
 		return -NLE_BAD_SOCK;
 
-	if (fcntl(sk->s_fd, F_SETFL, O_NONBLOCK) < 0)
+	if (fcntl (sk->s_fd, F_SETFL, O_NONBLOCK) < 0)
 		return -nl_syserr2nlerr (errno);
 
 	return 0;
@@ -982,25 +958,27 @@ nl_socket_add_memberships (struct nl_sock *sk, int group, ...)
 	if (sk->s_fd == -1)
 		return -NLE_BAD_SOCK;
 
-	va_start(ap, group);
+	va_start (ap, group);
 
 	while (group != 0) {
 		if (group < 0) {
-			va_end(ap);
+			va_end (ap);
 			g_return_val_if_reached (-NLE_BUG);
 		}
 
 		err = setsockopt (sk->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
 		                  &group, sizeof (group));
 		if (err < 0) {
-			va_end(ap);
-			return -nl_syserr2nlerr (errno);
+			int errsv = errno;
+
+			va_end (ap);
+			return -nl_syserr2nlerr (errsv);
 		}
 
-		group = va_arg(ap, int);
+		group = va_arg (ap, int);
 	}
 
-	va_end(ap);
+	va_end (ap);
 
 	return 0;
 }
@@ -1030,7 +1008,7 @@ void nl_socket_disable_msg_peek (struct nl_sock *sk)
 int
 nl_connect (struct nl_sock *sk, int protocol)
 {
-	int err;
+	int err, nlerr;
 	socklen_t addrlen;
 	struct sockaddr_nl local = { 0 };
 
@@ -1039,12 +1017,12 @@ nl_connect (struct nl_sock *sk, int protocol)
 
 	sk->s_fd = socket (AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
 	if (sk->s_fd < 0) {
-		err = -nl_syserr2nlerr (errno);
+		nlerr = -nl_syserr2nlerr (errno);
 		goto errout;
 	}
 
-	err = nl_socket_set_buffer_size(sk, 0, 0);
-	if (err < 0)
+	nlerr = nl_socket_set_buffer_size (sk, 0, 0);
+	if (nlerr < 0)
 		goto errout;
 
 	nm_assert (sk->s_local.nl_pid == 0);
@@ -1052,7 +1030,7 @@ nl_connect (struct nl_sock *sk, int protocol)
 	err = bind (sk->s_fd, (struct sockaddr*) &sk->s_local,
 	            sizeof (sk->s_local));
 	if (err != 0) {
-		err = -nl_syserr2nlerr (errno);
+		nlerr = -nl_syserr2nlerr (errno);
 		goto errout;
 	}
 
@@ -1060,17 +1038,17 @@ nl_connect (struct nl_sock *sk, int protocol)
 	err = getsockname (sk->s_fd, (struct sockaddr *) &local,
 	                   &addrlen);
 	if (err < 0) {
-		err = -nl_syserr2nlerr (errno);
+		nlerr = -nl_syserr2nlerr (errno);
 		goto errout;
 	}
 
 	if (addrlen != sizeof (local)) {
-		err = -NLE_UNSPEC;
+		nlerr = -NLE_UNSPEC;
 		goto errout;
 	}
 
 	if (local.nl_family != AF_NETLINK) {
-		err = -NLE_UNSPEC;
+		nlerr = -NLE_UNSPEC;
 		goto errout;
 	}
 
@@ -1081,10 +1059,10 @@ nl_connect (struct nl_sock *sk, int protocol)
 
 errout:
 	if (sk->s_fd != -1) {
-		close(sk->s_fd);
+		close (sk->s_fd);
 		sk->s_fd = -1;
 	}
-	return err;
+	return nlerr;
 }
 
 /*****************************************************************************/
@@ -1100,7 +1078,7 @@ _cb_init (struct nl_cb *dst, const struct nl_cb *src)
 		memset (dst, 0, sizeof (*dst));
 }
 
-static int ack_wait_handler(struct nl_msg *msg, void *arg)
+static int ack_wait_handler (struct nl_msg *msg, void *arg)
 {
 	return NL_STOP;
 }
@@ -1121,16 +1099,22 @@ do { \
 	const struct nl_cb *_cb = (cb); \
 	\
 	if (_cb->type##_cb) { \
-		err = _cb->type##_cb ((msg), _cb->type##_arg); \
-		switch (err) { \
+		/* the returned value here must be either a negative
+		 * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */ \
+		nlerr = _cb->type##_cb ((msg), _cb->type##_arg); \
+		switch (nlerr) { \
 		case NL_OK: \
-			err = 0; \
+			nlerr = 0; \
 			break; \
 		case NL_SKIP: \
 			goto skip; \
 		case NL_STOP: \
 			goto stop; \
 		default: \
+			if (nlerr >= 0) { \
+				nm_assert_not_reached (); \
+				nlerr = -NLE_BUG; \
+			} \
 			goto out; \
 		} \
 	} \
@@ -1139,7 +1123,7 @@ do { \
 int
 nl_recvmsgs (struct nl_sock *sk, const struct nl_cb *cb)
 {
-	int n, err = 0, multipart = 0, interrupted = 0, nrecv = 0;
+	int n, nlerr = 0, multipart = 0, interrupted = 0, nrecv = 0;
 	gs_free unsigned char *buf = NULL;
 	struct nlmsghdr *hdr;
 	struct sockaddr_nl nla = { 0 };
@@ -1164,9 +1148,9 @@ continue_reading:
 		nrecv++;
 
 		/* Only do sequence checking if auto-ack mode is enabled */
-		if (!(sk->s_flags & NL_NO_AUTO_ACK)) {
+		if (! (sk->s_flags & NL_NO_AUTO_ACK)) {
 			if (hdr->nlmsg_seq != sk->s_seq_expect) {
-				err = -NLE_SEQ_MISMATCH;
+				nlerr = -NLE_SEQ_MISMATCH;
 				goto out;
 			}
 		}
@@ -1198,7 +1182,7 @@ continue_reading:
 		 * this action by skipping this packet. */
 		if (hdr->nlmsg_type == NLMSG_DONE) {
 			multipart = 0;
-			NL_CB_CALL(cb, finish, msg);
+			NL_CB_CALL (cb, finish, msg);
 		}
 
 		/* Message to be ignored, the default action is to
@@ -1212,50 +1196,53 @@ continue_reading:
 		 * quit parsing. The user may overrule this action by retuning
 		 * NL_SKIP or NL_PROCEED (dangerous) */
 		else if (hdr->nlmsg_type == NLMSG_OVERRUN) {
-			err = -NLE_MSG_OVERFLOW;
+			nlerr = -NLE_MSG_OVERFLOW;
 			goto out;
 		}
 
 		/* Message carries a nlmsgerr */
 		else if (hdr->nlmsg_type == NLMSG_ERROR) {
-			struct nlmsgerr *e = nlmsg_data(hdr);
+			struct nlmsgerr *e = nlmsg_data (hdr);
 
-			if (hdr->nlmsg_len < nlmsg_size(sizeof (*e))) {
+			if (hdr->nlmsg_len < nlmsg_size (sizeof (*e))) {
 				/* Truncated error message, the default action
 				 * is to stop parsing. The user may overrule
 				 * this action by returning NL_SKIP or
 				 * NL_PROCEED (dangerous) */
-				err = -NLE_MSG_TRUNC;
+				nlerr = -NLE_MSG_TRUNC;
 				goto out;
 			}
 			if (e->error) {
 				/* Error message reported back from kernel. */
 				if (cb->err_cb) {
-					err = cb->err_cb (&nla, e,
-					                  cb->err_arg);
-					if (err < 0)
+					/* the returned value here must be either a negative
+					 * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */
+					nlerr = cb->err_cb (&nla, e,
+					                    cb->err_arg);
+					if (nlerr < 0)
 						goto out;
-					else if (err == NL_SKIP)
+					else if (nlerr == NL_SKIP)
 						goto skip;
-					else if (err == NL_STOP) {
-						err = -e->error;
+					else if (nlerr == NL_STOP) {
+						nlerr = -nl_syserr2nlerr (e->error);
 						goto out;
 					}
+					nm_assert (nlerr == NL_OK);
 				} else {
-					err = -e->error;
+					nlerr = -nl_syserr2nlerr (e->error);
 					goto out;
 				}
 			} else
-				NL_CB_CALL(cb, ack, msg);
+				NL_CB_CALL (cb, ack, msg);
 		} else {
 			/* Valid message (not checking for MULTIPART bit to
 			 * get along with broken kernels. NL_SKIP has no
 			 * effect on this.  */
-			NL_CB_CALL(cb, valid, msg);
+			NL_CB_CALL (cb, valid, msg);
 		}
 skip:
-		err = 0;
-		hdr = nlmsg_next(hdr, &n);
+		nlerr = 0;
+		hdr = nlmsg_next (hdr, &n);
 	}
 
 	if (multipart) {
@@ -1267,13 +1254,14 @@ skip:
 	}
 
 stop:
-	err = 0;
+	nlerr = 0;
 
 out:
 	if (interrupted)
-		err = -NLE_DUMP_INTR;
+		nlerr = -NLE_DUMP_INTR;
 
-	return err ?: nrecv;
+	nm_assert (nlerr <= 0);
+	return nlerr ?: nrecv;
 }
 
 int
@@ -1286,7 +1274,7 @@ nl_sendmsg (struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr)
 
 	nlmsg_set_src (msg, &sk->s_local);
 
-	ret = sendmsg(sk->s_fd, hdr, 0);
+	ret = sendmsg (sk->s_fd, hdr, 0);
 	if (ret < 0)
 		return -nl_syserr2nlerr (errno);
 
@@ -1304,31 +1292,31 @@ nl_send_iovec (struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsign
 		.msg_iov = iov,
 		.msg_iovlen = iovlen,
 	};
-	char buf[CMSG_SPACE(sizeof (struct ucred))];
+	char buf[CMSG_SPACE (sizeof (struct ucred))];
 
 	/* Overwrite destination if specified in the message itself, defaults
 	 * to the peer address of the socket.
 	 */
-	dst = nlmsg_get_dst(msg);
+	dst = nlmsg_get_dst (msg);
 	if (dst->nl_family == AF_NETLINK)
 		hdr.msg_name = dst;
 
 	/* Add credentials if present. */
-	creds = nlmsg_get_creds(msg);
+	creds = nlmsg_get_creds (msg);
 	if (creds != NULL) {
 		struct cmsghdr *cmsg;
 
 		hdr.msg_control = buf;
 		hdr.msg_controllen = sizeof (buf);
 
-		cmsg = CMSG_FIRSTHDR(&hdr);
+		cmsg = CMSG_FIRSTHDR (&hdr);
 		cmsg->cmsg_level = SOL_SOCKET;
 		cmsg->cmsg_type = SCM_CREDENTIALS;
-		cmsg->cmsg_len = CMSG_LEN(sizeof (struct ucred));
-		memcpy(CMSG_DATA(cmsg), creds, sizeof (struct ucred));
+		cmsg->cmsg_len = CMSG_LEN (sizeof (struct ucred));
+		memcpy (CMSG_DATA (cmsg), creds, sizeof (struct ucred));
 	}
 
-	return nl_sendmsg(sk, msg, &hdr);
+	return nl_sendmsg (sk, msg, &hdr);
 }
 
 void
@@ -1336,9 +1324,9 @@ nl_complete_msg (struct nl_sock *sk, struct nl_msg *msg)
 {
 	struct nlmsghdr *nlh;
 
-	nlh = nlmsg_hdr(msg);
+	nlh = nlmsg_hdr (msg);
 	if (nlh->nlmsg_pid == NL_AUTO_PORT)
-		nlh->nlmsg_pid = nl_socket_get_local_port(sk);
+		nlh->nlmsg_pid = nl_socket_get_local_port (sk);
 
 	if (nlh->nlmsg_seq == NL_AUTO_SEQ)
 		nlh->nlmsg_seq = sk->s_seq_next++;
@@ -1356,18 +1344,18 @@ int
 nl_send (struct nl_sock *sk, struct nl_msg *msg)
 {
 	struct iovec iov = {
-		.iov_base = (void *) nlmsg_hdr(msg),
-		.iov_len = nlmsg_hdr(msg)->nlmsg_len,
+		.iov_base = (void *) nlmsg_hdr (msg),
+		.iov_len = nlmsg_hdr (msg)->nlmsg_len,
 	};
 
-	return nl_send_iovec(sk, msg, &iov, 1);
+	return nl_send_iovec (sk, msg, &iov, 1);
 }
 
-int nl_send_auto(struct nl_sock *sk, struct nl_msg *msg)
+int nl_send_auto (struct nl_sock *sk, struct nl_msg *msg)
 {
-	nl_complete_msg(sk, msg);
+	nl_complete_msg (sk, msg);
 
-	return nl_send(sk, msg);
+	return nl_send (sk, msg);
 }
 
 int
@@ -1397,9 +1385,9 @@ nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla,
 		flags |= MSG_PEEK | MSG_TRUNC;
 
 	if (page_size == 0)
-		page_size = getpagesize() * 4;
+		page_size = getpagesize () * 4;
 
-	iov.iov_len = sk->s_bufsize ? : page_size;
+	iov.iov_len = sk->s_bufsize ?: page_size;
 	iov.iov_base = g_malloc (iov.iov_len);
 
 	if (   creds
@@ -1409,7 +1397,7 @@ nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla,
 	}
 
 retry:
-	n = recvmsg(sk->s_fd, &msg, flags);
+	n = recvmsg (sk->s_fd, &msg, flags);
 	if (!n) {
 		retval = 0;
 		goto abort;
@@ -1465,12 +1453,12 @@ retry:
 	if (creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
 		struct cmsghdr *cmsg;
 
-		for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+		for (cmsg = CMSG_FIRSTHDR (&msg); cmsg; cmsg = CMSG_NXTHDR (&msg, cmsg)) {
 			if (cmsg->cmsg_level != SOL_SOCKET)
 				continue;
 			if (cmsg->cmsg_type != SCM_CREDENTIALS)
 				continue;
-			tmpcreds = g_memdup (CMSG_DATA(cmsg), sizeof (*tmpcreds));
+			tmpcreds = nm_memdup (CMSG_DATA (cmsg), sizeof (*tmpcreds));
 			break;
 		}
 	}
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
index 185269ba..d5df7ab9 100644
--- a/src/platform/nm-netlink.h
+++ b/src/platform/nm-netlink.h
@@ -52,32 +52,48 @@
 #endif
 
 static inline int
-nl_errno (int err)
+nl_errno (int nlerr)
 {
-	/* the error codes from our netlink implementation are plain errno
-	 * extended with our own error in a particular range starting from
-	 * _NLE_BASE.
+	/* Normalizes an netlink error to be positive. Various API returns negative
+	 * error codes, and this function converts the negative value to its
+	 * positive.
 	 *
-	 * However, often we encode errors as negative values. This function
-	 * normalizes the error and returns its positive value. */
-	return err >= 0
-	       ? err
-	       : ((err == G_MININT) ? NLE_BUG : -errno);
+	 * It's very similar to nm_errno(), but not exactly. The difference is that
+	 * nm_errno() is for plain errno, while nl_errno() is for netlink error numbers.
+	 * Yes, netlink error number are ~almost~ the same as errno, except that a particular
+	 * range (_NLE_BASE, _NLE_BASE_END) is reserved. The difference between the two
+	 * functions is only how G_MININT is mapped.
+	 *
+	 * See also nl_syserr2nlerr() below. */
+	return nlerr >= 0
+	       ? nlerr
+	       : ((nlerr == G_MININT) ? NLE_BUG : -nlerr);
 }
 
 static inline int
-nl_syserr2nlerr (int err)
+nl_syserr2nlerr (int errsv)
 {
-	if (err == G_MININT)
+	/* this maps a native errno to a (always non-negative) netlink error number.
+	 *
+	 * Note that netlink error numbers are embedded into the range of regular
+	 * errno. The only difference is, that netlink error numbers reserve a
+	 * range (_NLE_BASE, _NLE_BASE_END) for their own purpose.
+	 *
+	 * That means, converting an errno to netlink error number means in
+	 * most cases just returning itself (negative values are normalized
+	 * to be positive). Only values G_MININT and [_NLE_BASE, _NLE_BASE_END]
+	 * are coerced to the special value NLE_NATIVE_ERRNO, as they cannot
+	 * otherwise be represented in netlink error number domain. */
+	if (errsv == G_MININT)
 		return NLE_NATIVE_ERRNO;
-	if (err < 0)
-		err = -err;
-	return (err >= _NLE_BASE && err < _NLE_BASE_END)
+	if (errsv < 0)
+		errsv = -errsv;
+	return (errsv >= _NLE_BASE && errsv < _NLE_BASE_END)
 	       ? NLE_NATIVE_ERRNO
-	       : err;
+	       : errsv;
 }
 
-const char *nl_geterror (int err);
+const char *nl_geterror (int nlerr);
 
 /*****************************************************************************/
 
@@ -177,6 +193,12 @@ nla_get_u8 (const struct nlattr *nla)
 }
 
 static inline uint8_t
+nla_get_s8 (const struct nlattr *nla)
+{
+	return *(const int8_t *) nla_data (nla);
+}
+
+static inline uint8_t
 nla_get_u8_cond (/*const*/ struct nlattr *const*tb, int attr, uint8_t default_val)
 {
 	nm_assert (tb);
@@ -197,6 +219,12 @@ nla_get_u32(const struct nlattr *nla)
 	return *(const uint32_t *) nla_data (nla);
 }
 
+static inline int32_t
+nla_get_s32(const struct nlattr *nla)
+{
+	return *(const int32_t *) nla_data (nla);
+}
+
 uint64_t nla_get_u64 (const struct nlattr *nla);
 
 static inline char *
@@ -232,12 +260,18 @@ nla_put_string (struct nl_msg *msg, int attrtype, const char *str)
 #define NLA_PUT_U8(msg, attrtype, value) \
 	NLA_PUT_TYPE(msg, uint8_t, attrtype, value)
 
+#define NLA_PUT_S8(msg, attrtype, value) \
+	NLA_PUT_TYPE(msg, int8_t, attrtype, value)
+
 #define NLA_PUT_U16(msg, attrtype, value) \
 	NLA_PUT_TYPE(msg, uint16_t, attrtype, value)
 
 #define NLA_PUT_U32(msg, attrtype, value) \
 	NLA_PUT_TYPE(msg, uint32_t, attrtype, value)
 
+#define NLA_PUT_S32(msg, attrtype, value) \
+	NLA_PUT_TYPE(msg, int32_t, attrtype, value)
+
 #define NLA_PUT_U64(msg, attrtype, value) \
 	NLA_PUT_TYPE(msg, uint64_t, attrtype, value)
 
@@ -252,7 +286,7 @@ struct nlattr *nla_find (const struct nlattr *head, int len, int attrtype);
 static inline int
 nla_ok (const struct nlattr *nla, int remaining)
 {
-	return remaining >= sizeof(*nla) &&
+	return remaining >= (int) sizeof(*nla) &&
 	       nla->nla_len >= sizeof(*nla) &&
 	       nla->nla_len <= remaining;
 }
@@ -296,8 +330,6 @@ struct nl_msg *nlmsg_alloc (void);
 
 struct nl_msg *nlmsg_alloc_size (size_t max);
 
-struct nl_msg *nlmsg_alloc_inherit (struct nlmsghdr *hdr);
-
 struct nl_msg *nlmsg_alloc_convert (struct nlmsghdr *hdr);
 
 struct nl_msg *nlmsg_alloc_simple (int nlmsgtype, int flags);
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 0d224443..216b1547 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -36,12 +36,15 @@
 
 #include "nm-utils.h"
 #include "nm-setting-wired.h"
+#include "nm-ethtool-utils.h"
 
 #include "nm-core-utils.h"
 
-/******************************************************************
+#define ONOFF(bool_val) ((bool_val) ? "on" : "off")
+
+/******************************************************************************
  * utils
- ******************************************************************/
+ *****************************************************************************/
 
 extern char *if_indextoname (unsigned __ifindex, char *__ifname);
 unsigned if_nametoindex (const char *__ifname);
@@ -63,9 +66,45 @@ nmp_utils_if_nametoindex (const char *ifname)
 	return if_nametoindex (ifname);
 }
 
-/******************************************************************
+/*****************************************************************************/
+
+typedef struct {
+	int fd;
+	int ifindex;
+	char ifname[IFNAMSIZ];
+} SocketHandle;
+
+static int
+socket_handle_init (SocketHandle *shandle, int ifindex)
+{
+	if (!nmp_utils_if_indextoname (ifindex, shandle->ifname)) {
+		shandle->ifindex = 0;
+		return -ENODEV;
+	}
+
+	shandle->fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+	if (shandle->fd < 0) {
+		shandle->ifindex = 0;
+		return -errno;
+	}
+
+	shandle->ifindex = ifindex;
+	return 0;
+}
+
+static void
+socket_handle_destroy (SocketHandle *shandle)
+{
+	if (shandle->ifindex) {
+		shandle->ifindex = 0;
+		nm_close (shandle->fd);
+	}
+}
+#define nm_auto_socket_handle nm_auto(socket_handle_destroy)
+
+/******************************************************************************
  * ethtool
- ******************************************************************/
+ *****************************************************************************/
 
 NM_UTILS_ENUM2STR_DEFINE_STATIC (_ethtool_cmd_to_string, guint32,
 	NM_UTILS_ENUM2STR (ETHTOOL_GDRVINFO,   "ETHTOOL_GDRVINFO"),
@@ -77,6 +116,7 @@ NM_UTILS_ENUM2STR_DEFINE_STATIC (_ethtool_cmd_to_string, guint32,
 	NM_UTILS_ENUM2STR (ETHTOOL_GSTATS,     "ETHTOOL_GSTATS"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GSTRINGS,   "ETHTOOL_GSTRINGS"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GWOL,       "ETHTOOL_GWOL"),
+	NM_UTILS_ENUM2STR (ETHTOOL_SFEATURES,  "ETHTOOL_SFEATURES"),
 	NM_UTILS_ENUM2STR (ETHTOOL_SSET,       "ETHTOOL_SSET"),
 	NM_UTILS_ENUM2STR (ETHTOOL_SWOL,       "ETHTOOL_SWOL"),
 );
@@ -87,6 +127,8 @@ _ethtool_data_to_string (gconstpointer edata, char *buf, gsize len)
 	return _ethtool_cmd_to_string (*((guint32 *) edata), buf, len);
 }
 
+/*****************************************************************************/
+
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
 #define ethtool_cmd_speed(pedata) ((pedata)->speed)
 
@@ -94,117 +136,553 @@ _ethtool_data_to_string (gconstpointer edata, char *buf, gsize len)
 	G_STMT_START { (pedata)->speed = (guint16) (speed); } G_STMT_END
 #endif
 
-static gboolean
-ethtool_get (int ifindex, gpointer edata)
+static int
+ethtool_call_handle (SocketHandle *shandle, gpointer edata)
 {
-	char ifname[IFNAMSIZ];
+	struct ifreq ifr = {
+		.ifr_data = edata,
+	};
 	char sbuf[50];
+	int errsv;
+
+	nm_assert (shandle);
+	nm_assert (shandle->ifindex);
+	nm_assert (shandle->ifname[0]);
+	nm_assert (strlen (shandle->ifname) < IFNAMSIZ);
+	nm_assert (edata);
+
+	memcpy (ifr.ifr_name, shandle->ifname, IFNAMSIZ);
+	if (ioctl (shandle->fd, SIOCETHTOOL, &ifr) < 0) {
+		errsv = errno;
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: failed: %s",
+		              shandle->ifindex,
+		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
+		              shandle->ifname,
+		              strerror (errsv));
+		return -errsv;
+	}
 
-	nm_assert (ifindex > 0);
+	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: success",
+	              shandle->ifindex,
+	              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
+	              shandle->ifname);
+	return 0;
+}
 
-	/* ethtool ioctl API uses the ifname to refer to an interface. That is racy
-	 * as interfaces can be renamed *sigh*.
-	 *
-	 * Note that we anyway have to verify whether the interface exists, before
-	 * calling ioctl for a non-existing ifname. This is to prevent autoloading
-	 * of kernel modules *sigh*.
-	 * Thus, as we anyway verify the existence of ifname before doing the call,
-	 * go one step further and lookup the ifname everytime anew.
-	 *
-	 * This does not solve the renaming race, but it minimizes the time for
-	 * the race to happen as much as possible. */
+static int
+ethtool_call_ifindex (int ifindex, gpointer edata)
+{
+	nm_auto_socket_handle SocketHandle shandle = { };
+	int r;
+	char sbuf[50];
 
-	if (!nmp_utils_if_indextoname (ifindex, ifname)) {
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: request fails resolving ifindex: %s",
+	nm_assert (edata);
+
+	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
 		              ifindex,
 		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-		              g_strerror (errno));
+		              g_strerror (-r));
+		return r;
+	}
+
+	return ethtool_call_handle (&shandle, edata);
+}
+
+/*****************************************************************************/
+
+static struct ethtool_gstrings *
+ethtool_get_stringset (SocketHandle *shandle, int stringset_id)
+{
+	struct {
+		struct ethtool_sset_info info;
+		guint32 sentinel;
+	} sset_info = { };
+	gs_free struct ethtool_gstrings *gstrings = NULL;
+	guint32 i, len;
+
+	sset_info.info.cmd = ETHTOOL_GSSET_INFO;
+	sset_info.info.reserved = 0;
+	sset_info.info.sset_mask = (1ULL << stringset_id);
+
+	if (ethtool_call_handle (shandle, &sset_info) < 0)
+		return NULL;
+	if (!sset_info.info.sset_mask)
+		return NULL;
+
+	len = sset_info.info.data[0];
+
+	gstrings = g_malloc0 (sizeof (*gstrings) + (len * ETH_GSTRING_LEN));
+	gstrings->cmd = ETHTOOL_GSTRINGS;
+	gstrings->string_set = stringset_id;
+	gstrings->len = len;
+	if (gstrings->len > 0) {
+		if (ethtool_call_handle (shandle, gstrings) < 0)
+			return NULL;
+		for (i = 0; i < gstrings->len; i++) {
+			/* ensure NUL terminated */
+			gstrings->data[i * ETH_GSTRING_LEN + (ETH_GSTRING_LEN - 1)] = '\0';
+		}
+	}
+
+	return g_steal_pointer (&gstrings);
+}
+
+static int
+ethtool_gstrings_find (const struct ethtool_gstrings *gstrings, const char *needle)
+{
+	guint32 i;
+
+	/* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN.
+	 * that means, we cannot possibly request longer names. */
+	nm_assert (needle && strlen (needle) < ETH_GSTRING_LEN);
+
+	for (i = 0; i < gstrings->len; i++) {
+		if (nm_streq ((char *) &gstrings->data[i * ETH_GSTRING_LEN], needle))
+			return i;
+	}
+	return -1;
+}
+
+static int
+ethtool_get_stringset_index (SocketHandle *shandle, int stringset_id, const char *needle)
+{
+	gs_free struct ethtool_gstrings *gstrings = NULL;
+
+	/* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN.
+	 * that means, we cannot possibly request longer names. */
+	nm_assert (needle && strlen (needle) < ETH_GSTRING_LEN);
+
+	gstrings = ethtool_get_stringset (shandle, stringset_id);
+	if (gstrings)
+		return ethtool_gstrings_find (gstrings, needle);
+	return -1;
+}
+
+/*****************************************************************************/
+
+static const NMEthtoolFeatureInfo _ethtool_feature_infos[_NM_ETHTOOL_ID_FEATURE_NUM] = {
+#define ETHT_FEAT(eid, ...) \
+	{ \
+		.ethtool_id = eid, \
+		.n_kernel_names = NM_NARG (__VA_ARGS__), \
+		.kernel_names = ((const char *const[]) { __VA_ARGS__ }), \
+	}
+
+	/* the order does only matter for one thing: if it happens that more than one NMEthtoolID
+	 * reference the same kernel-name, then the one that is mentioned *later* will win in
+	 * case these NMEthtoolIDs are set. That mostly only makes sense for ethtool-ids which
+	 * refer to multiple features ("feature-tso"), while also having more specific ids
+	 * ("feature-tx-tcp-segmentation"). */
+
+	/* names from ethtool utility, which are aliases for multiple features. */
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_SG,                           "tx-scatter-gather",
+	                                                               "tx-scatter-gather-fraglist"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TSO,                          "tx-tcp-segmentation",
+	                                                               "tx-tcp-ecn-segmentation",
+	                                                               "tx-tcp-mangleid-segmentation",
+	                                                               "tx-tcp6-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX,                           "tx-checksum-ipv4",
+	                                                               "tx-checksum-ip-generic",
+	                                                               "tx-checksum-ipv6",
+	                                                               "tx-checksum-fcoe-crc",
+	                                                               "tx-checksum-sctp"),
+
+	/* names from ethtool utility, which are aliases for one feature. */
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_GRO,                          "rx-gro"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_GSO,                          "tx-generic-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_LRO,                          "rx-lro"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_NTUPLE,                       "rx-ntuple-filter"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX,                           "rx-checksum"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RXHASH,                       "rx-hashing"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RXVLAN,                       "rx-vlan-hw-parse"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TXVLAN,                       "tx-vlan-hw-insert"),
+
+	/* names of features, as known by kernel. */
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD,               "esp-hw-offload"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD,       "esp-tx-csum-hw-offload"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_FCOE_MTU,                     "fcoe-mtu"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_HIGHDMA,                      "highdma"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD,                "hw-tc-offload"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD,               "l2-fwd-offload"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_LOOPBACK,                     "loopback"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_ALL,                       "rx-all"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_FCS,                       "rx-fcs"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_GRO_HW,                    "rx-gro-hw"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD,   "rx-udp_tunnel-port-offload"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER,               "rx-vlan-filter"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER,          "rx-vlan-stag-filter"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE,        "rx-vlan-stag-hw-parse"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD,                "tls-hw-record"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD,            "tls-hw-tx-offload"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC,         "tx-checksum-fcoe-crc"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4,             "tx-checksum-ipv4"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6,             "tx-checksum-ipv6"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC,       "tx-checksum-ip-generic"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP,             "tx-checksum-sctp"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION,          "tx-esp-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION,         "tx-fcoe-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION,     "tx-gre-csum-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION,          "tx-gre-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL,               "tx-gso-partial"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST,                "tx-gso-robust"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION,       "tx-ipxip4-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION,       "tx-ipxip6-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY,              "tx-nocache-copy"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER,            "tx-scatter-gather"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST,   "tx-scatter-gather-fraglist"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION,         "tx-sctp-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION,         "tx-tcp6-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION,      "tx-tcp-ecn-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, "tx-tcp-mangleid-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION,          "tx-tcp-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION,          "tx-udp-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION,      "tx-udp_tnl-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT,       "tx-vlan-stag-hw-insert"),
+};
+
+/* the number of kernel features that we handle. It essentially is the sum of all
+ * kernel_names. So, all ethtool-ids that reference exactly one kernel-name
+ * (_NM_ETHTOOL_ID_FEATURE_NUM) + some extra, for ethtool-ids that are aliases
+ * for multiple kernel-names. */
+#define N_ETHTOOL_KERNEL_FEATURES (((guint) _NM_ETHTOOL_ID_FEATURE_NUM) + 8u)
+
+static void
+_ASSERT_ethtool_feature_infos (void)
+{
+#if NM_MORE_ASSERTS > 10
+	guint i, k, n;
+	bool found[_NM_ETHTOOL_ID_FEATURE_NUM] = { };
+
+	G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (_ethtool_feature_infos) == _NM_ETHTOOL_ID_FEATURE_NUM);
+
+	n = 0;
+	for (i = 0; i < G_N_ELEMENTS (_ethtool_feature_infos); i++) {
+		NMEthtoolFeatureState kstate;
+		const NMEthtoolFeatureInfo *inf = &_ethtool_feature_infos[i];
+
+		g_assert (inf->ethtool_id >= _NM_ETHTOOL_ID_FEATURE_FIRST);
+		g_assert (inf->ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST);
+		g_assert (inf->n_kernel_names > 0);
+
+		for (k = 0; k < i; k++)
+			g_assert (inf->ethtool_id != _ethtool_feature_infos[k].ethtool_id);
+
+		g_assert (!found[inf->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]);
+		found[inf->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST] = TRUE;
+
+		kstate.idx_kernel_name = inf->n_kernel_names - 1;
+		g_assert ((guint) kstate.idx_kernel_name == (guint) (inf->n_kernel_names - 1));
+
+		n += inf->n_kernel_names;
+		for (k = 0; k < inf->n_kernel_names; k++) {
+			g_assert (nm_utils_strv_find_first ((char **) inf->kernel_names,
+			                                    k,
+			                                    inf->kernel_names[k]) < 0);
+		}
+	}
+
+	for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++)
+		g_assert (found[i]);
+
+	g_assert (n == N_ETHTOOL_KERNEL_FEATURES);
+#endif
+}
+
+static NMEthtoolFeatureStates *
+ethtool_get_features (SocketHandle *shandle)
+{
+	gs_free NMEthtoolFeatureStates *states = NULL;
+	gs_free struct ethtool_gstrings *ss_features = NULL;
+
+	_ASSERT_ethtool_feature_infos ();
+
+	ss_features = ethtool_get_stringset (shandle, ETH_SS_FEATURES);
+	if (!ss_features)
+		return NULL;
+
+	if (ss_features->len > 0) {
+		gs_free struct ethtool_gfeatures *gfeatures = NULL;
+		guint idx;
+		const NMEthtoolFeatureState *states_list0 = NULL;
+		const NMEthtoolFeatureState *const*states_plist0 = NULL;
+		guint states_plist_n = 0;
+
+		gfeatures = g_malloc0 (  sizeof (struct ethtool_gfeatures)
+		                       + (NM_DIV_ROUND_UP (ss_features->len, 32u) * sizeof(gfeatures->features[0])));
+
+		gfeatures->cmd = ETHTOOL_GFEATURES;
+		gfeatures->size = NM_DIV_ROUND_UP (ss_features->len, 32u);
+		if (ethtool_call_handle (shandle, gfeatures) < 0)
+			return NULL;
+
+		for (idx = 0; idx < G_N_ELEMENTS (_ethtool_feature_infos); idx++) {
+			const NMEthtoolFeatureInfo *info = &_ethtool_feature_infos[idx];
+			guint idx_kernel_name;
+
+			for (idx_kernel_name = 0; idx_kernel_name < info->n_kernel_names; idx_kernel_name++) {
+				NMEthtoolFeatureState *kstate;
+				const char *kernel_name = info->kernel_names[idx_kernel_name];
+				int i_feature;
+				guint i_block;
+				guint32 i_flag;
+
+				i_feature = ethtool_gstrings_find (ss_features, kernel_name);
+				if (i_feature < 0)
+					continue;
+
+				i_block = ((guint) i_feature) / 32u;
+				i_flag = (guint32) (1u << (((guint) i_feature) % 32u));
+
+				if (!states) {
+					states = g_malloc0 (sizeof (NMEthtoolFeatureStates)
+					                    + (N_ETHTOOL_KERNEL_FEATURES * sizeof (NMEthtoolFeatureState))
+					                    + ((N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos)) * sizeof (NMEthtoolFeatureState *)));
+					states_list0 = &states->states_list[0];
+					states_plist0 = (gpointer) &states_list0[N_ETHTOOL_KERNEL_FEATURES];
+					states->n_ss_features = ss_features->len;
+				}
+
+				nm_assert (states->n_states < N_ETHTOOL_KERNEL_FEATURES);
+				kstate = (NMEthtoolFeatureState *) &states_list0[states->n_states];
+				states->n_states++;
+
+				kstate->info = info;
+				kstate->idx_ss_features = i_feature;
+				kstate->idx_kernel_name = idx_kernel_name;
+				kstate->available     = !!(gfeatures->features[i_block].available     & i_flag);
+				kstate->requested     = !!(gfeatures->features[i_block].requested     & i_flag);
+				kstate->active        = !!(gfeatures->features[i_block].active        & i_flag);
+				kstate->never_changed = !!(gfeatures->features[i_block].never_changed & i_flag);
+
+				nm_assert (states_plist_n < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos));
+
+				if (!states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST])
+					states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST] = &states_plist0[states_plist_n];
+				((const NMEthtoolFeatureState **) states_plist0)[states_plist_n] = kstate;
+				states_plist_n++;
+			}
+
+			if (states && states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]) {
+				nm_assert (states_plist_n < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos));
+				nm_assert (!states_plist0[states_plist_n]);
+				states_plist_n++;
+			}
+		}
+	}
+
+	return g_steal_pointer (&states);
+}
+
+NMEthtoolFeatureStates *
+nmp_utils_ethtool_get_features (int ifindex)
+{
+	nm_auto_socket_handle SocketHandle shandle = { };
+	NMEthtoolFeatureStates *features;
+	int r;
+
+	g_return_val_if_fail (ifindex > 0, 0);
+
+	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
+		              ifindex,
+		              "get-features",
+		              g_strerror (-r));
 		return FALSE;
 	}
 
-	{
-		nm_auto_close int fd = -1;
-		struct ifreq ifr = {
-			.ifr_data = edata,
-		};
+	features = ethtool_get_features (&shandle);
 
-		memcpy (ifr.ifr_name, ifname, sizeof (ifname));
+	if (!features) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure getting features",
+		              ifindex,
+		              "get-features");
+		return NULL;
+	}
 
-		fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-		if (fd < 0) {
-			nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: failed creating socket for ioctl: %s",
-			              ifindex,
-			              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-			              ifname,
-			              g_strerror (errno));
-			return FALSE;
+	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: retrieved kernel features",
+	              ifindex,
+	              "get-features");
+	return features;
+}
+
+static const char *
+_ethtool_feature_state_to_string (char *buf, gsize buf_size, const NMEthtoolFeatureState *s, const char *prefix)
+{
+	int l;
+
+	l = g_snprintf (buf, buf_size,
+	                "%s %s%s",
+	                prefix ?: "",
+	                ONOFF (s->active),
+	                (!s->available || s->never_changed)
+	                  ? ", [fixed]"
+	                  : ((s->requested != s->active)
+	                       ? (s->requested ? ", [requested on]" : ", [requested off]")
+	                       : ""));
+	nm_assert (l < buf_size);
+	return buf;
+}
+
+gboolean
+nmp_utils_ethtool_set_features (int ifindex,
+                                const NMEthtoolFeatureStates *features,
+                                const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+                                gboolean do_set /* or reset */)
+{
+	nm_auto_socket_handle SocketHandle shandle = { };
+	gs_free struct ethtool_sfeatures *sfeatures = NULL;
+	int r;
+	guint i, j;
+	struct {
+		const NMEthtoolFeatureState *f_state;
+		NMTernary requested;
+	} set_states[N_ETHTOOL_KERNEL_FEATURES];
+	guint set_states_n = 0;
+	gboolean success = TRUE;
+
+	g_return_val_if_fail (ifindex > 0, 0);
+	g_return_val_if_fail (features, 0);
+	g_return_val_if_fail (requested, 0);
+
+	nm_assert (features->n_states <= N_ETHTOOL_KERNEL_FEATURES);
+
+	for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) {
+		const NMEthtoolFeatureState *const*states_indexed;
+
+		if (requested[i] == NM_TERNARY_DEFAULT)
+			continue;
+
+		if (!(states_indexed = features->states_indexed[i])) {
+			if (do_set) {
+				nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: set feature %s: skip (not found)",
+				              ifindex,
+				              "set-features",
+				              nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname);
+				success = FALSE;
+			}
+			continue;
 		}
 
-		if (ioctl (fd, SIOCETHTOOL, &ifr) < 0) {
-			nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: failed: %s",
+		for (j = 0; states_indexed[j]; j++) {
+			const NMEthtoolFeatureState *s = states_indexed[j];
+			char sbuf[255];
+
+			if (set_states_n >= G_N_ELEMENTS (set_states))
+				g_return_val_if_reached (FALSE);
+
+			if (s->never_changed) {
+				nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: %s feature %s (%s): %s, %s (skip feature marked as never changed)",
+				              ifindex,
+				              "set-features",
+				              do_set ? "set" : "reset",
+				              nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
+				              s->info->kernel_names[s->idx_kernel_name],
+				              ONOFF (do_set ? requested[i] == NM_TERNARY_TRUE : s->active),
+				              _ethtool_feature_state_to_string (sbuf, sizeof (sbuf), s, do_set ? " currently:" : " before:"));
+				continue;
+			}
+
+			nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: %s feature %s (%s): %s, %s",
 			              ifindex,
-			              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-			              ifname,
-			              strerror (errno));
-			return FALSE;
+			              "set-features",
+			              do_set ? "set" : "reset",
+			              nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
+			              s->info->kernel_names[s->idx_kernel_name],
+			              ONOFF (do_set ? requested[i] == NM_TERNARY_TRUE : s->active),
+			              _ethtool_feature_state_to_string (sbuf, sizeof (sbuf), s, do_set ? " currently:" : " before:"));
+
+			if (   do_set
+			    && (!s->available || s->never_changed)
+			    && (s->active != (requested[i] == NM_TERNARY_TRUE))) {
+				/* we request to change a flag which kernel reported as fixed.
+				 * While the ethtool operation will silently succeed, mark the request
+				 * as failure. */
+				success = FALSE;
+			}
+
+			set_states[set_states_n].f_state = s;
+			set_states[set_states_n].requested = requested[i];
+			set_states_n++;
 		}
+	}
 
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: success",
+	if (set_states_n == 0) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: no feature requested",
 		              ifindex,
-		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-		              ifname);
+		              "set-features");
 		return TRUE;
 	}
-}
 
-static int
-ethtool_get_stringset_index (int ifindex, int stringset_id, const char *string)
-{
-	gs_free struct ethtool_sset_info *info = NULL;
-	gs_free struct ethtool_gstrings *strings = NULL;
-	guint32 len, i;
+	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
+		              ifindex,
+		              "set-features",
+		              g_strerror (-r));
+		return FALSE;
+	}
 
-	g_return_val_if_fail (ifindex > 0, -1);
+	sfeatures = g_malloc0 (sizeof (struct ethtool_sfeatures)
+	                       + (NM_DIV_ROUND_UP (features->n_ss_features, 32U) * sizeof(sfeatures->features[0])));
+	sfeatures->cmd = ETHTOOL_SFEATURES;
+	sfeatures->size = NM_DIV_ROUND_UP (features->n_ss_features, 32U);
 
-	info = g_malloc0 (sizeof (*info) + sizeof (guint32));
-	info->cmd = ETHTOOL_GSSET_INFO;
-	info->reserved = 0;
-	info->sset_mask = 1ULL << stringset_id;
+	for (i = 0; i < set_states_n; i++) {
+		const NMEthtoolFeatureState *s = set_states[i].f_state;
+		guint i_block;
+		guint32 i_flag;
+		gboolean is_requested;
 
-	if (!ethtool_get (ifindex, info))
-		return -1;
-	if (!info->sset_mask)
-		return -1;
+		i_block = s->idx_ss_features / 32u;
+		i_flag = (guint32) (1u << (s->idx_ss_features % 32u));
 
-	len = info->data[0];
+		sfeatures->features[i_block].valid |= i_flag;
 
-	strings = g_malloc0 (sizeof (*strings) + len * ETH_GSTRING_LEN);
-	strings->cmd = ETHTOOL_GSTRINGS;
-	strings->string_set = stringset_id;
-	strings->len = len;
-	if (!ethtool_get (ifindex, strings))
-		return -1;
+		if (do_set)
+			is_requested = (set_states[i].requested == NM_TERNARY_TRUE);
+		else
+			is_requested = s->active;
 
-	for (i = 0; i < len; i++) {
-		if (!strcmp ((char *) &strings->data[i * ETH_GSTRING_LEN], string))
-			return i;
+		if (is_requested)
+			sfeatures->features[i_block].requested |= i_flag;
+		else
+			sfeatures->features[i_block].requested &= ~i_flag;
 	}
 
-	return -1;
+	if ((r = ethtool_call_handle (&shandle, sfeatures)) < 0) {
+		success = FALSE;
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure setting features (%s)",
+		              ifindex,
+		              "set-features",
+		              g_strerror (-r));
+		return FALSE;
+	}
+
+	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: %s",
+	              ifindex,
+	              "set-features",
+	              success
+	                ? "successfully setting features"
+	                : "at least some of the features were not successfuly set");
+	return success;
 }
 
+/*****************************************************************************/
+
 gboolean
 nmp_utils_ethtool_get_driver_info (int ifindex,
                                    NMPUtilsEthtoolDriverInfo *data)
 {
 	struct ethtool_drvinfo *drvinfo;
-	G_STATIC_ASSERT (sizeof (*data) == sizeof (*drvinfo));
-	G_STATIC_ASSERT (offsetof (NMPUtilsEthtoolDriverInfo, driver)     == offsetof (struct ethtool_drvinfo, driver));
-	G_STATIC_ASSERT (offsetof (NMPUtilsEthtoolDriverInfo, version)    == offsetof (struct ethtool_drvinfo, version));
-	G_STATIC_ASSERT (offsetof (NMPUtilsEthtoolDriverInfo, fw_version) == offsetof (struct ethtool_drvinfo, fw_version));
-	G_STATIC_ASSERT (sizeof (data->driver)     == sizeof (drvinfo->driver));
-	G_STATIC_ASSERT (sizeof (data->version)    == sizeof (drvinfo->version));
-	G_STATIC_ASSERT (sizeof (data->fw_version) == sizeof (drvinfo->fw_version));
+
+	G_STATIC_ASSERT_EXPR (sizeof (*data) == sizeof (*drvinfo));
+	G_STATIC_ASSERT_EXPR (offsetof (NMPUtilsEthtoolDriverInfo, driver)     == offsetof (struct ethtool_drvinfo, driver));
+	G_STATIC_ASSERT_EXPR (offsetof (NMPUtilsEthtoolDriverInfo, version)    == offsetof (struct ethtool_drvinfo, version));
+	G_STATIC_ASSERT_EXPR (offsetof (NMPUtilsEthtoolDriverInfo, fw_version) == offsetof (struct ethtool_drvinfo, fw_version));
+	G_STATIC_ASSERT_EXPR (sizeof (data->driver)     == sizeof (drvinfo->driver));
+	G_STATIC_ASSERT_EXPR (sizeof (data->version)    == sizeof (drvinfo->version));
+	G_STATIC_ASSERT_EXPR (sizeof (data->fw_version) == sizeof (drvinfo->fw_version));
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (data, FALSE);
@@ -213,7 +691,7 @@ nmp_utils_ethtool_get_driver_info (int ifindex,
 
 	memset (drvinfo, 0, sizeof (*drvinfo));
 	drvinfo->cmd = ETHTOOL_GDRVINFO;
-	return ethtool_get (ifindex, drvinfo);
+	return ethtool_call_ifindex (ifindex, drvinfo) >= 0;
 }
 
 gboolean
@@ -233,7 +711,7 @@ nmp_utils_ethtool_get_permanent_address (int ifindex,
 	edata.e.cmd = ETHTOOL_GPERMADDR;
 	edata.e.size = NM_UTILS_HWADDR_LEN_MAX;
 
-	if (!ethtool_get (ifindex, &edata.e))
+	if (ethtool_call_ifindex (ifindex, &edata.e) < 0)
 		return FALSE;
 
 	if (edata.e.size > NM_UTILS_HWADDR_LEN_MAX)
@@ -252,8 +730,8 @@ nmp_utils_ethtool_get_permanent_address (int ifindex,
 		}
 		return FALSE;
 	}
-not_all_0or1:
 
+not_all_0or1:
 	memcpy (buf, edata.e.data, edata.e.size);
 	*length = edata.e.size;
 	return TRUE;
@@ -270,20 +748,30 @@ nmp_utils_ethtool_supports_carrier_detect (int ifindex)
 	 * assume the device supports carrier-detect, otherwise we assume it
 	 * doesn't.
 	 */
-	return ethtool_get (ifindex, &edata);
+	return ethtool_call_ifindex (ifindex, &edata) >= 0;
 }
 
 gboolean
 nmp_utils_ethtool_supports_vlans (int ifindex)
 {
+	nm_auto_socket_handle SocketHandle shandle = { };
+	int r;
 	gs_free struct ethtool_gfeatures *features = NULL;
 	int idx, block, bit, size;
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	idx = ethtool_get_stringset_index (ifindex, ETH_SS_FEATURES, "vlan-challenged");
-	if (idx == -1) {
-		nm_log_dbg (LOGD_PLATFORM, "ethtool: vlan-challenged ethtool feature does not exist for %d?", ifindex);
+	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
+		              ifindex,
+		              "support-vlans",
+		              g_strerror (-r));
+		return FALSE;
+	}
+
+	idx = ethtool_get_stringset_index (&shandle, ETH_SS_FEATURES, "vlan-challenged");
+	if (idx < 0) {
+		nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: vlan-challenged ethtool feature does not exist?", ifindex);
 		return FALSE;
 	}
 
@@ -295,7 +783,7 @@ nmp_utils_ethtool_supports_vlans (int ifindex)
 	features->cmd = ETHTOOL_GFEATURES;
 	features->size = size;
 
-	if (!ethtool_get (ifindex, features))
+	if (ethtool_call_handle (&shandle, features) < 0)
 		return FALSE;
 
 	return !(features->features[block].active & (1 << bit));
@@ -304,21 +792,32 @@ nmp_utils_ethtool_supports_vlans (int ifindex)
 int
 nmp_utils_ethtool_get_peer_ifindex (int ifindex)
 {
+	nm_auto_socket_handle SocketHandle shandle = { };
+	int r;
+
 	gs_free struct ethtool_stats *stats = NULL;
 	int peer_ifindex_stat;
 
 	g_return_val_if_fail (ifindex > 0, 0);
 
-	peer_ifindex_stat = ethtool_get_stringset_index (ifindex, ETH_SS_STATS, "peer_ifindex");
-	if (peer_ifindex_stat == -1) {
-		nm_log_dbg (LOGD_PLATFORM, "ethtool: peer_ifindex stat for %d does not exist?", ifindex);
+	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
+		              ifindex,
+		              "get-peer-ifindex",
+		              g_strerror (-r));
+		return FALSE;
+	}
+
+	peer_ifindex_stat = ethtool_get_stringset_index (&shandle, ETH_SS_STATS, "peer_ifindex");
+	if (peer_ifindex_stat < 0) {
+		nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: peer_ifindex stat does not exist?", ifindex);
 		return FALSE;
 	}
 
 	stats = g_malloc0 (sizeof (*stats) + (peer_ifindex_stat + 1) * sizeof (guint64));
 	stats->cmd = ETHTOOL_GSTATS;
 	stats->n_stats = peer_ifindex_stat + 1;
-	if (!ethtool_get (ifindex, stats))
+	if (ethtool_call_ifindex (ifindex, stats) < 0)
 		return 0;
 
 	return stats->data[peer_ifindex_stat];
@@ -333,7 +832,7 @@ nmp_utils_ethtool_get_wake_on_lan (int ifindex)
 
 	memset (&wol, 0, sizeof (wol));
 	wol.cmd = ETHTOOL_GWOL;
-	if (!ethtool_get (ifindex, &wol))
+	if (ethtool_call_ifindex (ifindex, &wol) < 0)
 		return FALSE;
 
 	return wol.wolopts != 0;
@@ -351,7 +850,7 @@ nmp_utils_ethtool_get_link_settings (int ifindex,
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	if (!ethtool_get (ifindex, &edata))
+	if (ethtool_call_ifindex (ifindex, &edata) < 0)
 		return FALSE;
 
 	if (out_autoneg)
@@ -433,9 +932,13 @@ nmp_utils_ethtool_set_link_settings (int ifindex,
 	                      || (!speed && duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN), FALSE);
 
 	/* retrieve first current settings */
-	if (!ethtool_get (ifindex, &edata))
+	if (ethtool_call_ifindex (ifindex, &edata) < 0)
 		return FALSE;
 
+	/* FIXME: try first new ETHTOOL_GLINKSETTINGS/SLINKSETTINGS API
+	 * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3f1ac7a700d039c61d8d8b99f28d605d489a60cf
+	 */
+
 	/* then change the needed ones */
 	edata.cmd = ETHTOOL_SSET;
 	if (autoneg) {
@@ -485,7 +988,7 @@ nmp_utils_ethtool_set_link_settings (int ifindex,
 		}
 	}
 
-	return ethtool_get (ifindex, &edata);
+	return ethtool_call_ifindex (ifindex, &edata) >= 0;
 }
 
 gboolean
@@ -500,8 +1003,8 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex,
 	if (wol == NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE)
 		return TRUE;
 
-	nm_log_dbg (LOGD_PLATFORM, "setting Wake-on-LAN options 0x%x, password '%s'",
-	            (unsigned) wol, wol_password);
+	nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: setting Wake-on-LAN options 0x%x, password '%s'",
+	            ifindex, (unsigned) wol, wol_password);
 
 	wol_info.cmd = ETHTOOL_SWOL;
 	wol_info.wolopts = 0;
@@ -521,45 +1024,41 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex,
 
 	if (wol_password) {
 		if (!nm_utils_hwaddr_aton (wol_password, wol_info.sopass, ETH_ALEN)) {
-			nm_log_dbg (LOGD_PLATFORM, "couldn't parse Wake-on-LAN password '%s'", wol_password);
+			nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: couldn't parse Wake-on-LAN password '%s'", ifindex, wol_password);
 			return FALSE;
 		}
 		wol_info.wolopts |= WAKE_MAGICSECURE;
 	}
 
-	return ethtool_get (ifindex, &wol_info);
+	return ethtool_call_ifindex (ifindex, &wol_info) >= 0;
 }
 
-/******************************************************************
+/******************************************************************************
  * mii
- ******************************************************************/
+ *****************************************************************************/
 
 gboolean
 nmp_utils_mii_supports_carrier_detect (int ifindex)
 {
-	char ifname[IFNAMSIZ];
-	nm_auto_close int fd = -1;
+	nm_auto_socket_handle SocketHandle shandle = { };
+	int r;
 	struct ifreq ifr;
 	struct mii_ioctl_data *mii;
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	if (!nmp_utils_if_indextoname (ifindex, ifname)) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d]: carrier-detect no: request fails resolving ifindex: %s", ifindex, g_strerror (errno));
-		return FALSE;
-	}
-
-	fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-	if (fd < 0) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: couldn't open control socket: %s", ifindex, ifname, g_strerror (errno));
+	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "mii[%d]: carrier-detect no: failed creating ethtool socket: %s",
+		              ifindex,
+		              g_strerror (-r));
 		return FALSE;
 	}
 
 	memset (&ifr, 0, sizeof (struct ifreq));
-	memcpy (ifr.ifr_name, ifname, IFNAMSIZ);
+	memcpy (ifr.ifr_name, shandle.ifname, IFNAMSIZ);
 
-	if (ioctl (fd, SIOCGMIIPHY, &ifr) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIPHY failed: %s", ifindex, ifname, strerror (errno));
+	if (ioctl (shandle.fd, SIOCGMIIPHY, &ifr) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIPHY failed: %s", ifindex, shandle.ifname, strerror (errno));
 		return FALSE;
 	}
 
@@ -567,18 +1066,18 @@ nmp_utils_mii_supports_carrier_detect (int ifindex)
 	mii = (struct mii_ioctl_data *) &ifr.ifr_ifru;
 	mii->reg_num = MII_BMSR;
 
-	if (ioctl (fd, SIOCGMIIREG, &ifr) != 0) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIREG failed: %s", ifindex, ifname, strerror (errno));
+	if (ioctl (shandle.fd, SIOCGMIIREG, &ifr) != 0) {
+		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIREG failed: %s", ifindex, shandle.ifname, strerror (errno));
 		return FALSE;
 	}
 
-	nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X", ifindex, ifname, mii->val_out);
+	nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X", ifindex, shandle.ifname, mii->val_out);
 	return TRUE;
 }
 
-/******************************************************************
+/******************************************************************************
  * udev
- ******************************************************************/
+ *****************************************************************************/
 
 const char *
 nmp_utils_udev_get_driver (struct udev_device *udevice)
diff --git a/src/platform/nm-platform-utils.h b/src/platform/nm-platform-utils.h
index 97f5630a..bad77aee 100644
--- a/src/platform/nm-platform-utils.h
+++ b/src/platform/nm-platform-utils.h
@@ -23,6 +23,9 @@
 
 #include "nm-platform.h"
 #include "nm-setting-wired.h"
+#include "nm-ethtool-utils.h"
+
+/*****************************************************************************/
 
 const char *nmp_utils_ethtool_get_driver (int ifindex);
 gboolean nmp_utils_ethtool_supports_carrier_detect (int ifindex);
@@ -35,6 +38,10 @@ gboolean nmp_utils_ethtool_set_wake_on_lan (int ifindex, NMSettingWiredWakeOnLan
 gboolean nmp_utils_ethtool_get_link_settings (int ifindex, gboolean *out_autoneg, guint32 *out_speed, NMPlatformLinkDuplexType *out_duplex);
 gboolean nmp_utils_ethtool_set_link_settings (int ifindex, gboolean autoneg, guint32 speed, NMPlatformLinkDuplexType duplex);
 
+gboolean  nmp_utils_ethtool_get_permanent_address (int ifindex,
+                                                   guint8 *buf,
+                                                   size_t *length);
+
 typedef struct {
 	/* We don't want to include <linux/ethtool.h> in header files,
 	 * thus create a ABI compatible version of struct ethtool_drvinfo.*/
@@ -55,9 +62,52 @@ typedef struct {
 gboolean nmp_utils_ethtool_get_driver_info (int ifindex,
                                             NMPUtilsEthtoolDriverInfo *data);
 
-gboolean  nmp_utils_ethtool_get_permanent_address (int ifindex,
-                                                   guint8 *buf,
-                                                   size_t *length);
+typedef struct {
+	NMEthtoolID ethtool_id;
+
+	guint8 n_kernel_names;
+
+	/* one NMEthtoolID refers to one or more kernel_names. The reason for supporting this complexity
+	 * (where one NMSettingEthtool option refers to multiple kernel features)  is to follow what
+	 * ethtool does, where "tx" is an alias for multiple features. */
+	const char *const*kernel_names;
+} NMEthtoolFeatureInfo;
+
+typedef struct {
+	const NMEthtoolFeatureInfo *info;
+
+	guint idx_ss_features;
+
+	/* one NMEthtoolFeatureInfo references one or more kernel_names. This is the index
+	 * of the matching info->kernel_names */
+	guint8 idx_kernel_name;
+
+	bool available:1;
+	bool requested:1;
+	bool active:1;
+	bool never_changed:1;
+} NMEthtoolFeatureState;
+
+struct _NMEthtoolFeatureStates {
+	guint n_states;
+
+	guint n_ss_features;
+
+	/* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */
+	const NMEthtoolFeatureState *const*states_indexed[_NM_ETHTOOL_ID_FEATURE_NUM];
+
+	/* the same content, here as a list of n_states entries. */
+	const NMEthtoolFeatureState states_list[];
+};
+
+NMEthtoolFeatureStates *nmp_utils_ethtool_get_features (int ifindex);
+
+gboolean nmp_utils_ethtool_set_features (int ifindex,
+                                         const NMEthtoolFeatureStates *features,
+                                         const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+                                         gboolean do_set /* or reset */);
+
+/*****************************************************************************/
 
 gboolean nmp_utils_mii_supports_carrier_detect (int ifindex);
 
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index f75019e8..7ddcf41a 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2012 - 2017 Red Hat, Inc.
+ * Copyright (C) 2012 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -27,6 +27,8 @@
 #include <unistd.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
 #include <string.h>
 #include <linux/ip.h>
 #include <linux/if_tun.h>
@@ -74,8 +76,6 @@ G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OF
         } \
     } G_STMT_END
 
-#define LOG_FMT_IP_TUNNEL "adding %s '%s' parent %u local %s remote %s"
-
 /*****************************************************************************/
 
 static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = { 0 };
@@ -807,6 +807,7 @@ _nm_platform_link_get_by_address_match_link (const NMPObject *obj, struct _nm_pl
  **/
 const NMPlatformLink *
 nm_platform_link_get_by_address (NMPlatform *self,
+                                 NMLinkType link_type,
                                  gconstpointer address,
                                  size_t length)
 {
@@ -827,7 +828,7 @@ nm_platform_link_get_by_address (NMPlatform *self,
 		g_return_val_if_reached (NULL);
 
 	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (self),
-	                                  0, NULL, TRUE, NM_LINK_TYPE_NONE,
+	                                  0, NULL, TRUE, link_type,
 	                                  (NMPObjectMatchFn) _nm_platform_link_get_by_address_match_link, &d);
 	return NMP_OBJECT_CAST_LINK (obj);
 }
@@ -901,7 +902,7 @@ nm_platform_link_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("link: adding link '%s' of type '%s' (%d)"
+	_LOGD ("link: adding link '%s': %s (%d)"
 	       "%s%s" /* address */
 	       "%s%s" /* veth peer */
 	       "",
@@ -985,7 +986,7 @@ nm_platform_link_set_netns (NMPlatform *self, int ifindex, int netns_fd)
 	if (!pllink)
 		return FALSE;
 
-	_LOGD ("link: ifindex %d changing network namespace to %d", ifindex, netns_fd);
+	_LOGD ("link: move link %d to network namespace with fd %d", ifindex, netns_fd);
 	return klass->link_set_netns (self, ifindex, netns_fd);
 }
 
@@ -1354,15 +1355,18 @@ nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolea
 NMPlatformError
 nm_platform_link_set_address (NMPlatform *self, int ifindex, gconstpointer address, size_t length)
 {
+	gs_free char *mac = NULL;
+
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
 	g_return_val_if_fail (ifindex > 0, NM_PLATFORM_ERROR_BUG);
 	g_return_val_if_fail (address, NM_PLATFORM_ERROR_BUG);
 	g_return_val_if_fail (length > 0, NM_PLATFORM_ERROR_BUG);
 
-	_LOGD ("link: setting %s (%d) hardware address",
+	_LOGD ("link: setting %s (%d) hardware address to %s",
 	       nm_strquote_a (20, nm_platform_link_get_name (self, ifindex)),
-	       ifindex);
+	       ifindex,
+	       (mac = nm_utils_hwaddr_ntoa (address, length)));
 	return klass->link_set_address (self, ifindex, address, length);
 }
 
@@ -1459,18 +1463,52 @@ nm_platform_link_supports_sriov (NMPlatform *self, int ifindex)
 	return klass->link_supports_sriov (self, ifindex);
 }
 
+/**
+ * nm_platform_link_set_sriov_params:
+ * @self: platform instance
+ * @ifindex: the index of the interface to change
+ * @num_vfs: the number of VFs to create
+ * @autoprobe: -1 to keep the current autoprobe-drivers value,
+ *   or {0,1} to set a new value
+ */
 gboolean
-nm_platform_link_set_sriov_num_vfs (NMPlatform *self, int ifindex, guint num_vfs)
+nm_platform_link_set_sriov_params (NMPlatform *self,
+                                   int ifindex,
+                                   guint num_vfs,
+                                   int autoprobe)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (NM_IN_SET (autoprobe, -1, 0, 1), FALSE);
 
-	_LOGD ("link: setting %u VFs for %s (%d)",
+	_LOGD ("link: setting %u total VFs and autoprobe %d for %s (%d)",
 	       num_vfs,
+	       autoprobe,
 	       nm_strquote_a (25, nm_platform_link_get_name (self, ifindex)),
 	       ifindex);
-	return klass->link_set_sriov_num_vfs (self, ifindex, num_vfs);
+	return klass->link_set_sriov_params (self, ifindex, num_vfs, autoprobe);
+}
+
+gboolean
+nm_platform_link_set_sriov_vfs (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs)
+{
+	guint i;
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	_LOGD ("link: setting VFs for \"%s\" (%d):",
+	       nm_platform_link_get_name (self, ifindex),
+	       ifindex);
+
+	for (i = 0; vfs[i]; i++) {
+		const NMPlatformVF *vf = vfs[i];
+
+		_LOGD ("link:   VF %s", nm_platform_vf_to_string (vf, NULL, 0));
+	}
+
+	return klass->link_set_sriov_vfs (self, ifindex, vfs);
 }
 
 /**
@@ -1852,6 +1890,12 @@ nm_platform_link_get_lnk_gre (NMPlatform *self, int ifindex, const NMPlatformLin
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_GRE, out_link);
 }
 
+const NMPlatformLnkGre *
+nm_platform_link_get_lnk_gretap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_GRETAP, out_link);
+}
+
 const NMPlatformLnkInfiniband *
 nm_platform_link_get_lnk_infiniband (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
 {
@@ -1864,6 +1908,18 @@ nm_platform_link_get_lnk_ip6tnl (NMPlatform *self, int ifindex, const NMPlatform
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_IP6TNL, out_link);
 }
 
+const NMPlatformLnkIp6Tnl *
+nm_platform_link_get_lnk_ip6gre (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_IP6GRE, out_link);
+}
+
+const NMPlatformLnkIp6Tnl *
+nm_platform_link_get_lnk_ip6gretap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_IP6GRETAP, out_link);
+}
+
 const NMPlatformLnkIpIp *
 nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
 {
@@ -1912,6 +1968,12 @@ nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformL
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_VXLAN, out_link);
 }
 
+const NMPlatformLnkWireGuard *
+nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_WIREGUARD, out_link);
+}
+
 /*****************************************************************************/
 
 /**
@@ -1996,8 +2058,9 @@ nm_platform_link_vlan_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("link: adding vlan '%s' parent %d vlanid %d vlanflags %x",
+	_LOGD ("link: adding link '%s': vlan parent %d vlanid %d vlanflags %x",
 	       name, parent, vlanid, vlanflags);
+
 	if (!klass->vlan_add (self, name, parent, vlanid, vlanflags, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
 	return NM_PLATFORM_ERROR_SUCCESS;
@@ -2028,8 +2091,9 @@ nm_platform_link_vxlan_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("link: adding vxlan '%s' parent %d id %d",
-	       name, props->parent_ifindex, props->id);
+	_LOGD ("link: adding link '%s': %s",
+	       name, nm_platform_lnk_vxlan_to_string (props, NULL, 0));
+
 	if (!klass->link_vxlan_add (self, name, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
 	return NM_PLATFORM_ERROR_SUCCESS;
@@ -2080,13 +2144,78 @@ nm_platform_link_tun_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("link: adding tun '%s' %s",
+	_LOGD ("link: adding link '%s': %s",
 	       name, nm_platform_lnk_tun_to_string (props, b, sizeof (b)));
+
 	if (!klass->link_tun_add (self, name, props, out_link, out_fd))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
+/**
+ * nm_platform_6lowpan_add:
+ * @self: platform instance
+ * @parent: parent link
+ * @name: name of the new interface
+ * @out_link: on success, the link object
+ *
+ * Create a 6LoWPAN interface.
+ */
+NMPlatformError
+nm_platform_link_6lowpan_add (NMPlatform *self,
+                              const char *name,
+                              int parent,
+                              const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_6LOWPAN, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD ("adding link '%s': 6lowpan parent %u", name, parent);
+
+	if (!klass->link_6lowpan_add (self, name, parent, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+gboolean
+nm_platform_link_6lowpan_get_properties (NMPlatform *self, int ifindex, int *out_parent)
+{
+	const NMPlatformLink *plink;
+	_CHECK_SELF (self, klass, FALSE);
+
+	plink = nm_platform_link_get (self, ifindex);
+
+	if (!plink)
+		return FALSE;
+	if (plink->type != NM_LINK_TYPE_6LOWPAN)
+		return FALSE;
+
+	if (plink->parent != 0) {
+		NM_SET_OUT (out_parent, plink->parent);
+		return TRUE;
+	}
+
+	/* As of 4.16 kernel does not expose the peer_ifindex as IFA_LINK.
+	 * Find the WPAN device with the same MAC address. */
+	if (out_parent) {
+		const NMPlatformLink *parent_plink;
+
+		parent_plink = nm_platform_link_get_by_address (self, NM_LINK_TYPE_WPAN,
+		                                                plink->addr.data,
+		                                                plink->addr.len);
+		NM_SET_OUT (out_parent, parent_plink ? parent_plink->ifindex : -1);
+	}
+
+	return TRUE;
+}
+
 /*****************************************************************************/
 
 static gboolean
@@ -2310,23 +2439,18 @@ nm_platform_link_gre_add (NMPlatform *self,
                           const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
-	char buffer[INET_ADDRSTRLEN];
 
 	_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_GRE, out_link);
+	plerr = _link_add_check_existing (self, name, props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE, out_link);
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "gre",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet4_ntop (props->local, NULL),
-	       nm_utils_inet4_ntop (props->remote, buffer));
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_gre_to_string (props, NULL, 0));
 
 	if (!klass->link_gre_add (self, name, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -2372,6 +2496,9 @@ _infiniband_add_add_or_delete (NMPlatform *self,
 		if (!klass->infiniband_partition_add (self, parent, p_key, out_link))
 			return NM_PLATFORM_ERROR_UNSPECIFIED;
 	} else {
+		_LOGD ("link: deleting infiniband partition %s for parent '%s' (%d), key %d",
+		       name, parent_link->name, parent, p_key);
+
 		if (!klass->infiniband_partition_delete (self, parent, p_key))
 			return NM_PLATFORM_ERROR_UNSPECIFIED;
 	}
@@ -2472,23 +2599,19 @@ nm_platform_link_ip6tnl_add (NMPlatform *self,
                              const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
-	char buffer[INET6_ADDRSTRLEN];
 
 	_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);
+	g_return_val_if_fail (!props->is_gre, NM_PLATFORM_ERROR_BUG);
 
 	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_IP6TNL, out_link);
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "ip6tnl",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet6_ntop (&props->local, NULL),
-	       nm_utils_inet6_ntop (&props->remote, buffer));
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
 
 	if (!klass->link_ip6tnl_add (self, name, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -2496,6 +2619,46 @@ nm_platform_link_ip6tnl_add (NMPlatform *self,
 }
 
 /**
+ * nm_platform_ip6gre_add:
+ * @self: platform instance
+ * @name: name of the new interface
+ * @props: interface properties
+ * @out_link: on success, the link object
+ *
+ * Create an IPv6 GRE/GRETAP tunnel.
+ */
+NMPlatformError
+nm_platform_link_ip6gre_add (NMPlatform *self,
+                             const char *name,
+                             const NMPlatformLnkIp6Tnl *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);
+	g_return_val_if_fail (props->is_gre, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self,
+	                                  name,
+	                                  props->is_tap
+	                                      ? NM_LINK_TYPE_IP6GRETAP
+	                                      : NM_LINK_TYPE_IP6GRE,
+	                                  out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
+
+	if (!klass->link_ip6gre_add (self, name, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+/**
  * nm_platform_ipip_add:
  * @self: platform instance
  * @name: name of the new interface
@@ -2511,7 +2674,6 @@ nm_platform_link_ipip_add (NMPlatform *self,
                            const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
-	char buffer[INET_ADDRSTRLEN];
 
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
@@ -2522,12 +2684,8 @@ nm_platform_link_ipip_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "ipip",
-	       name,
-		   props->parent_ifindex,
-	       nm_utils_inet4_ntop (props->local, NULL),
-	       nm_utils_inet4_ntop (props->remote, buffer));
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_ipip_to_string (props, NULL, 0));
 
 	if (!klass->link_ipip_add (self, name, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -2538,6 +2696,7 @@ nm_platform_link_ipip_add (NMPlatform *self,
  * nm_platform_macsec_add:
  * @self: platform instance
  * @name: name of the new interface
+ * @parent: parent link
  * @props: interface properties
  * @out_link: on success, the link object
  *
@@ -2561,10 +2720,8 @@ nm_platform_link_macsec_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("adding macsec '%s' parent %u sci %llx",
-	       name,
-	       parent,
-	       (unsigned long long) props->sci);
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_macsec_to_string (props, NULL, 0));
 
 	if (!klass->link_macsec_add (self, name, parent, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -2601,11 +2758,8 @@ nm_platform_link_macvlan_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("adding %s '%s' parent %u mode %u",
-	       props->tap ? "macvtap" : "macvlan",
-	       name,
-	       parent,
-	       props->mode);
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_macvlan_to_string (props, NULL, 0));
 
 	if (!klass->link_macvlan_add (self, name, parent, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -2628,7 +2782,6 @@ nm_platform_link_sit_add (NMPlatform *self,
                           const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
-	char buffer[INET_ADDRSTRLEN];
 
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
@@ -2639,12 +2792,8 @@ nm_platform_link_sit_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "sit",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet4_ntop (props->local, NULL),
-	       nm_utils_inet4_ntop (props->remote, buffer));
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_sit_to_string (props, NULL, 0));
 
 	if (!klass->link_sit_add (self, name, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -2888,6 +3037,16 @@ nm_platform_wifi_indicate_addressing_running (NMPlatform *self, int ifindex, gbo
 	klass->wifi_indicate_addressing_running (self, ifindex, running);
 }
 
+NMSettingWirelessWakeOnWLan
+nm_platform_wifi_get_wake_on_wlan (NMPlatform *self, int ifindex)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wifi_get_wake_on_wlan (self, ifindex);
+}
+
 gboolean
 nm_platform_wifi_set_wake_on_wlan (NMPlatform *self, int ifindex, NMSettingWirelessWakeOnWLan wowl)
 {
@@ -2929,6 +3088,46 @@ nm_platform_mesh_set_ssid (NMPlatform *self, int ifindex, const guint8 *ssid, gs
 	return klass->mesh_set_ssid (self, ifindex, ssid, len);
 }
 
+guint16
+nm_platform_wpan_get_pan_id (NMPlatform *self, int ifindex)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wpan_get_pan_id (self, ifindex);
+}
+
+gboolean
+nm_platform_wpan_set_pan_id (NMPlatform *self, int ifindex, guint16 pan_id)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wpan_set_pan_id (self, ifindex, pan_id);
+}
+
+guint16
+nm_platform_wpan_get_short_addr (NMPlatform *self, int ifindex)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wpan_get_short_addr (self, ifindex);
+}
+
+gboolean
+nm_platform_wpan_set_short_addr (NMPlatform *self, int ifindex, guint16 short_addr)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wpan_set_short_addr (self, ifindex, short_addr);
+}
+
 #define TO_STRING_DEV_BUF_SIZE (5+15+1)
 static const char *
 _to_string_dev (NMPlatform *self, int ifindex, char *buf, size_t size)
@@ -3014,6 +3213,32 @@ NM_UTILS_LOOKUP_STR_DEFINE (nm_platform_link_duplex_type_to_string, NMPlatformLi
 
 /*****************************************************************************/
 
+NMEthtoolFeatureStates *
+nm_platform_ethtool_get_link_features (NMPlatform *self, int ifindex)
+{
+	_CHECK_SELF_NETNS (self, klass, netns, NULL);
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+
+	return nmp_utils_ethtool_get_features (ifindex);
+}
+
+gboolean
+nm_platform_ethtool_set_features (NMPlatform *self,
+                                  int ifindex,
+                                  const NMEthtoolFeatureStates *features,
+                                  const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+                                  gboolean do_set /* or reset */)
+{
+	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return nmp_utils_ethtool_set_features (ifindex, features, requested, do_set);
+}
+
+/*****************************************************************************/
+
 const NMDedupMultiHeadEntry *
 nm_platform_lookup_all (NMPlatform *platform,
                         NMPCacheIdType cache_id_type,
@@ -3622,7 +3847,7 @@ ip6_address_scope_priority (const struct in6_addr *addr)
 	return 3;
 }
 
-static gint
+static int
 ip6_address_scope_cmp (gconstpointer a, gconstpointer b)
 {
 	const NMPlatformIP6Address *x = NMP_OBJECT_CAST_IP6_ADDRESS (*(const void **) a);
@@ -4957,7 +5182,7 @@ nm_platform_lnk_gre_to_string (const NMPlatformLnkGre *lnk, char *buf, gsize len
 		return buf;
 
 	g_snprintf (buf, len,
-	            "gre"
+	            lnk->is_tap ? "gretap" : "gre"
 	            "%s" /* remote */
 	            "%s" /* local */
 	            "%s" /* parent_ifindex */
@@ -5014,12 +5239,18 @@ nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsi
 	char str_encap[30];
 	char str_proto[30];
 	char str_parent_ifindex[30];
+	char *str_type;
 
 	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
 		return buf;
 
+	if (lnk->is_gre)
+		str_type = lnk->is_tap ? "ip6gretap" : "ip6gre";
+	else
+		str_type = "ip6tnl";
+
 	g_snprintf (buf, len,
-	            "ip6tnl"
+	            "%s" /* type */
 	            "%s" /* remote */
 	            "%s" /* local */
 	            "%s" /* parent_ifindex */
@@ -5030,6 +5261,7 @@ nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsi
 	            "%s" /* proto */
 	            " flags 0x%x"
 	            "",
+	            str_type,
 	            nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet6_ntop (&lnk->remote, str_remote1)),
 	            nm_sprintf_buf (str_local, " local %s", nm_utils_inet6_ntop (&lnk->local, str_local1)),
 	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
@@ -5114,7 +5346,8 @@ nm_platform_lnk_macvlan_to_string (const NMPlatformLnkMacvlan *lnk, char *buf, g
 		return buf;
 
 	g_snprintf (buf, len,
-	            "macvlan mode %u %s",
+	            "%s mode %u %s",
+	            lnk->tap ? "macvtap" : "macvlan",
 	            lnk->mode,
 	            lnk->no_promisc ? "not-promisc" : "promisc");
 	return buf;
@@ -5298,6 +5531,91 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 	return buf;
 }
 
+const char *
+nm_platform_wireguard_peer_to_string (const NMPWireGuardPeer *peer, char *buf, gsize len)
+{
+	gs_free char *public_key_b64 = NULL;
+	char s_endpoint[NM_UTILS_INET_ADDRSTRLEN + 100];
+	char s_addr[NM_UTILS_INET_ADDRSTRLEN];
+	guint i;
+
+	nm_utils_to_string_buffer_init (&buf, &len);
+
+	if (peer->endpoint_family == AF_INET) {
+		nm_sprintf_buf (s_endpoint,
+		                " endpoint %s:%u",
+		                nm_utils_inet4_ntop (peer->endpoint_addr.addr4, s_addr),
+		                (guint) peer->endpoint_port);
+	} else if (peer->endpoint_family == AF_INET6) {
+		nm_sprintf_buf (s_endpoint,
+		                " endpoint [%s]:%u",
+		                nm_utils_inet6_ntop (&peer->endpoint_addr.addr6, s_addr),
+		                (guint) peer->endpoint_port);
+	} else
+		s_endpoint[0] = '\0';
+
+	public_key_b64 = g_base64_encode (peer->public_key, sizeof (peer->public_key));
+
+	nm_utils_strbuf_append (&buf, &len,
+	                        "public-key %s"
+	                        "%s" /* preshared-key */
+	                        "%s" /* endpoint */
+	                        " rx %"G_GUINT64_FORMAT
+	                        " tx %"G_GUINT64_FORMAT
+	                        "%s", /* allowed-ips */
+	                        public_key_b64,
+	                        nm_utils_mem_all_zero (peer->preshared_key, sizeof (peer->preshared_key))
+	                          ? ""
+	                          : " preshared-key (hidden)",
+	                        s_endpoint,
+	                        peer->rx_bytes,
+	                        peer->tx_bytes,
+	                        peer->allowed_ips_len > 0
+	                          ? " allowed-ips"
+	                          : "");
+
+	for (i = 0; i < peer->allowed_ips_len; i++) {
+		const NMPWireGuardAllowedIP *allowed_ip = &peer->allowed_ips[i];
+
+		nm_utils_strbuf_append (&buf, &len,
+		                        " %s/%u",
+		                        nm_utils_inet_ntop (allowed_ip->family, &allowed_ip->addr, s_addr),
+		                        allowed_ip->mask);
+	}
+
+	return buf;
+}
+
+const char *
+nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireGuard *lnk, char *buf, gsize len)
+{
+	gs_free char *public_b64 = NULL;
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	if (!nm_utils_mem_all_zero (lnk->public_key, sizeof (lnk->public_key)))
+		public_b64 = g_base64_encode (lnk->public_key, sizeof (lnk->public_key));
+
+	g_snprintf (buf, len,
+	            "wireguard"
+	            "%s%s" /* public-key */
+	            "%s" /* private-key */
+	            " listen-port %u"
+	            " fwmark 0x%x",
+	            public_b64
+	              ? " public-key "
+	              : "",
+	            public_b64 ?: "",
+	            nm_utils_mem_all_zero (lnk->private_key, sizeof (lnk->private_key))
+	              ? ""
+	              : " private-key (hidden)",
+	            lnk->listen_port,
+	            lnk->fwmark);
+
+	return buf;
+}
+
 /**
  * nm_platform_ip4_address_to_string:
  * @route: pointer to NMPlatformIP4Address address structure
@@ -5775,6 +6093,56 @@ nm_platform_tfilter_cmp (const NMPlatformTfilter *a, const NMPlatformTfilter *b)
 	return 0;
 }
 
+const char *
+nm_platform_vf_to_string (const NMPlatformVF *vf, char *buf, gsize len)
+{
+	char str_mac[128], mac[128];
+	char str_spoof_check[64];
+	char str_trust[64];
+	char str_min_tx_rate[64];
+	char str_max_tx_rate[64];
+	nm_auto_free_gstring GString *gstr_vlans = NULL;
+	guint i;
+
+	if (!nm_utils_to_string_buffer_init_null (vf, &buf, &len))
+		return buf;
+
+	if (vf->mac.len) {
+		nm_utils_hwaddr_ntoa_buf (vf->mac.data, vf->mac.len, TRUE, mac, sizeof (mac));
+		nm_sprintf_buf (str_mac, " mac %s", mac);
+	} else
+		str_mac[0] = '\0';
+
+	if (vf->num_vlans) {
+		gstr_vlans = g_string_new ("");
+		for (i = 0; i < vf->num_vlans; i++) {
+			g_string_append_printf (gstr_vlans, " vlan %u", (unsigned) vf->vlans[i].id);
+			if (vf->vlans[i].qos)
+				g_string_append_printf (gstr_vlans, " qos %u", (unsigned) vf->vlans[i].qos);
+			if (vf->vlans[i].proto_ad)
+				g_string_append (gstr_vlans, " proto 802.1ad");
+		}
+	}
+
+	g_snprintf (buf, len,
+	            "%u"    /* index */
+	            "%s"    /* MAC */
+	            "%s"    /* spoof check */
+	            "%s"    /* trust */
+	            "%s"    /* min tx rate */
+	            "%s"    /* max tx rate */
+	            "%s",   /* VLANs */
+	            vf->index,
+	            str_mac,
+	            vf->spoofchk >= 0 ? nm_sprintf_buf (str_spoof_check, " spoofchk %d", vf->spoofchk) : "",
+	            vf->trust >= 0 ? nm_sprintf_buf (str_trust, " trust %d", vf->trust) : "",
+	            vf->min_tx_rate ? nm_sprintf_buf (str_min_tx_rate, " min_tx_rate %u", (unsigned) vf->min_tx_rate) : "",
+	            vf->max_tx_rate ? nm_sprintf_buf (str_max_tx_rate, " max_tx_rate %u", (unsigned) vf->max_tx_rate) : "",
+	            gstr_vlans ? gstr_vlans->str : "");
+
+	return buf;
+}
+
 void
 nm_platform_link_hash_update (const NMPlatformLink *obj, NMHashState *h)
 {
@@ -5843,7 +6211,8 @@ nm_platform_lnk_gre_hash_update (const NMPlatformLnkGre *obj, NMHashState *h)
 	                     obj->output_key,
 	                     obj->ttl,
 	                     obj->tos,
-	                     (bool) obj->path_mtu_discovery);
+	                     (bool) obj->path_mtu_discovery,
+	                     (bool) obj->is_tap);
 }
 
 int
@@ -5860,6 +6229,7 @@ nm_platform_lnk_gre_cmp (const NMPlatformLnkGre *a, const NMPlatformLnkGre *b)
 	NM_CMP_FIELD (a, b, ttl);
 	NM_CMP_FIELD (a, b, tos);
 	NM_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
+	NM_CMP_FIELD_BOOL (a, b, is_tap);
 	return 0;
 }
 
@@ -5891,7 +6261,13 @@ nm_platform_lnk_ip6tnl_hash_update (const NMPlatformLnkIp6Tnl *obj, NMHashState
 	                     obj->encap_limit,
 	                     obj->proto,
 	                     obj->flow_label,
-	                     obj->flags);
+	                     obj->flags,
+	                     obj->input_flags,
+	                     obj->output_flags,
+	                     obj->input_key,
+	                     obj->output_key,
+	                     (bool) obj->is_gre,
+	                     (bool) obj->is_tap);
 }
 
 int
@@ -5907,6 +6283,12 @@ nm_platform_lnk_ip6tnl_cmp (const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6
 	NM_CMP_FIELD (a, b, flow_label);
 	NM_CMP_FIELD (a, b, proto);
 	NM_CMP_FIELD (a, b, flags);
+	NM_CMP_FIELD (a, b, input_flags);
+	NM_CMP_FIELD (a, b, output_flags);
+	NM_CMP_FIELD (a, b, input_key);
+	NM_CMP_FIELD (a, b, output_key);
+	NM_CMP_FIELD_BOOL (a, b, is_gre);
+	NM_CMP_FIELD_BOOL (a, b, is_tap);
 	return 0;
 }
 
@@ -6124,6 +6506,27 @@ nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan
 }
 
 void
+nm_platform_lnk_wireguard_hash_update (const NMPlatformLnkWireGuard *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->listen_port,
+	                     obj->fwmark);
+	nm_hash_update (h, obj->private_key, sizeof (obj->private_key));
+	nm_hash_update (h, obj->public_key, sizeof (obj->public_key));
+}
+
+int
+nm_platform_lnk_wireguard_cmp (const NMPlatformLnkWireGuard *a, const NMPlatformLnkWireGuard *b)
+{
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, listen_port);
+	NM_CMP_FIELD (a, b, fwmark);
+	NM_CMP_FIELD_MEMCMP (a, b, private_key);
+	NM_CMP_FIELD_MEMCMP (a, b, public_key);
+	return 0;
+}
+
+void
 nm_platform_ip4_address_hash_update (const NMPlatformIP4Address *obj, NMHashState *h)
 {
 	nm_hash_update_vals (h,
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 866df736..11495aff 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2009 - 2017 Red Hat, Inc.
+ * Copyright (C) 2009 - 2018 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_PLATFORM_H__
@@ -608,6 +608,26 @@ extern const NMPlatformVTableRoute nm_platform_vtable_route_v4;
 extern const NMPlatformVTableRoute nm_platform_vtable_route_v6;
 
 typedef struct {
+	guint16 id;
+	guint32 qos;
+	bool proto_ad:1;
+} NMPlatformVFVlan;
+
+typedef struct {
+	guint32 index;
+	guint32 min_tx_rate;
+	guint32 max_tx_rate;
+	guint num_vlans;
+	NMPlatformVFVlan *vlans;
+	struct {
+		guint8 data[20]; /* NM_UTILS_HWADDR_LEN_MAX */
+		guint8 len;
+	} mac;
+	gint8 spoofchk;
+	gint8 trust;
+} NMPlatformVF;
+
+typedef struct {
 	in_addr_t local;
 	in_addr_t remote;
 	int parent_ifindex;
@@ -618,6 +638,7 @@ typedef struct {
 	guint8 ttl;
 	guint8 tos;
 	bool path_mtu_discovery:1;
+	bool is_tap:1;
 } NMPlatformLnkGre;
 
 typedef struct {
@@ -635,6 +656,14 @@ typedef struct {
 	guint8 proto;
 	guint flow_label;
 	guint32 flags;
+
+	/* IP6GRE only */
+	guint32 input_key;
+	guint32 output_key;
+	guint16 input_flags;
+	guint16 output_flags;
+	bool is_tap:1;
+	bool is_gre:1;
 } NMPlatformLnkIp6Tnl;
 
 typedef struct {
@@ -723,6 +752,16 @@ typedef struct {
 	bool l3miss:1;
 } NMPlatformLnkVxlan;
 
+#define NMP_WIREGUARD_PUBLIC_KEY_LEN 32
+#define NMP_WIREGUARD_SYMMETRIC_KEY_LEN 32
+
+typedef struct {
+	guint32 fwmark;
+	guint16 listen_port;
+	guint8 private_key[NMP_WIREGUARD_PUBLIC_KEY_LEN];
+	guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN];
+} NMPlatformLnkWireGuard;
+
 typedef enum {
 	NM_PLATFORM_LINK_DUPLEX_UNKNOWN,
 	NM_PLATFORM_LINK_DUPLEX_HALF,
@@ -786,7 +825,8 @@ typedef struct {
 	NMPlatformError (*link_set_address) (NMPlatform *, int ifindex, gconstpointer address, size_t length);
 	NMPlatformError (*link_set_mtu) (NMPlatform *, int ifindex, guint32 mtu);
 	gboolean (*link_set_name) (NMPlatform *, int ifindex, const char *name);
-	gboolean (*link_set_sriov_num_vfs) (NMPlatform *, int ifindex, guint num_vfs);
+	gboolean (*link_set_sriov_params) (NMPlatform *, int ifindex, guint num_vfs, int autoprobe);
+	gboolean (*link_set_sriov_vfs) (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
 
 	char *   (*link_get_physical_port_id) (NMPlatform *, int ifindex);
 	guint    (*link_get_dev_id) (NMPlatform *, int ifindex);
@@ -829,6 +869,10 @@ typedef struct {
 	                             const char *name,
 	                             const NMPlatformLnkIp6Tnl *props,
 	                             const NMPlatformLink **out_link);
+	gboolean (*link_ip6gre_add) (NMPlatform *,
+	                             const char *name,
+	                             const NMPlatformLnkIp6Tnl *props,
+	                             const NMPlatformLink **out_link);
 	gboolean (*link_ipip_add) (NMPlatform *,
 	                           const char *name,
 	                           const NMPlatformLnkIpIp *props,
@@ -854,12 +898,16 @@ typedef struct {
 	                          const NMPlatformLink **out_link,
 	                          int *out_fd);
 
+	gboolean (*link_6lowpan_add) (NMPlatform *platform,
+	                              const char *name,
+	                              int parent,
+	                              const NMPlatformLink **out_link);
+
 	gboolean (*infiniband_partition_add) (NMPlatform *, int parent, int p_key, const NMPlatformLink **out_link);
 	gboolean (*infiniband_partition_delete) (NMPlatform *, int parent, int p_key);
 
 	gboolean    (*wifi_get_capabilities) (NMPlatform *, int ifindex, NMDeviceWifiCapabilities *caps);
 	gboolean    (*wifi_get_bssid)        (NMPlatform *, int ifindex, guint8 *bssid);
-	GByteArray *(*wifi_get_ssid)         (NMPlatform *, int ifindex);
 	guint32     (*wifi_get_frequency)    (NMPlatform *, int ifindex);
 	int         (*wifi_get_quality)      (NMPlatform *, int ifindex);
 	guint32     (*wifi_get_rate)         (NMPlatform *, int ifindex);
@@ -868,12 +916,18 @@ typedef struct {
 	void        (*wifi_set_powersave)    (NMPlatform *, int ifindex, guint32 powersave);
 	guint32     (*wifi_find_frequency)   (NMPlatform *, int ifindex, const guint32 *freqs);
 	void        (*wifi_indicate_addressing_running) (NMPlatform *, int ifindex, gboolean running);
+	NMSettingWirelessWakeOnWLan (*wifi_get_wake_on_wlan) (NMPlatform *, int ifindex);
 	gboolean    (*wifi_set_wake_on_wlan) (NMPlatform *, int ifindex, NMSettingWirelessWakeOnWLan wowl);
 
 	guint32     (*mesh_get_channel)      (NMPlatform *, int ifindex);
 	gboolean    (*mesh_set_channel)      (NMPlatform *, int ifindex, guint32 channel);
 	gboolean    (*mesh_set_ssid)         (NMPlatform *, int ifindex, const guint8 *ssid, gsize len);
 
+	guint16     (*wpan_get_pan_id)       (NMPlatform *, int ifindex);
+	gboolean    (*wpan_set_pan_id)       (NMPlatform *, int ifindex, guint16 pan_id);
+	guint16     (*wpan_get_short_addr)   (NMPlatform *, int ifindex);
+	gboolean    (*wpan_set_short_addr)   (NMPlatform *, int ifindex, guint16 short_addr);
+
 	gboolean (*object_delete) (NMPlatform *, const NMPObject *obj);
 
 	gboolean (*ip4_address_add) (NMPlatform *,
@@ -1078,7 +1132,7 @@ const NMPObject *nm_platform_link_get_obj (NMPlatform *self,
                                            gboolean visible_only);
 const NMPlatformLink *nm_platform_link_get (NMPlatform *self, int ifindex);
 const NMPlatformLink *nm_platform_link_get_by_ifname (NMPlatform *self, const char *ifname);
-const NMPlatformLink *nm_platform_link_get_by_address (NMPlatform *self, gconstpointer address, size_t length);
+const NMPlatformLink *nm_platform_link_get_by_address (NMPlatform *self, NMLinkType link_type, gconstpointer address, size_t length);
 
 GPtrArray *nm_platform_link_get_all (NMPlatform *self, gboolean sort_by_name);
 NMPlatformError nm_platform_link_dummy_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
@@ -1162,7 +1216,8 @@ gboolean nm_platform_link_get_permanent_address (NMPlatform *self, int ifindex,
 NMPlatformError nm_platform_link_set_address (NMPlatform *self, int ifindex, const void *address, size_t length);
 NMPlatformError nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu);
 gboolean nm_platform_link_set_name (NMPlatform *self, int ifindex, const char *name);
-gboolean nm_platform_link_set_sriov_num_vfs (NMPlatform *self, int ifindex, guint num_vfs);
+gboolean nm_platform_link_set_sriov_params (NMPlatform *self, int ifindex, guint num_vfs, int autoprobe);
+gboolean nm_platform_link_set_sriov_vfs (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
 
 char    *nm_platform_link_get_physical_port_id (NMPlatform *self, int ifindex);
 guint    nm_platform_link_get_dev_id (NMPlatform *self, int ifindex);
@@ -1187,7 +1242,10 @@ char *nm_platform_sysctl_slave_get_option (NMPlatform *self, int ifindex, const
 
 const NMPObject *nm_platform_link_get_lnk (NMPlatform *self, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link);
 const NMPlatformLnkGre *nm_platform_link_get_lnk_gre (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkGre *nm_platform_link_get_lnk_gretap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkIp6Tnl *nm_platform_link_get_lnk_ip6tnl (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkIp6Tnl *nm_platform_link_get_lnk_ip6gre (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkIp6Tnl *nm_platform_link_get_lnk_ip6gretap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkIpIp *nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkInfiniband *nm_platform_link_get_lnk_infiniband (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkIpIp *nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
@@ -1198,6 +1256,7 @@ const NMPlatformLnkSit *nm_platform_link_get_lnk_sit (NMPlatform *self, int ifin
 const NMPlatformLnkTun *nm_platform_link_get_lnk_tun (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkVlan *nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkVxlan *nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkWireGuard *nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 
 NMPlatformError nm_platform_link_vlan_add (NMPlatform *self,
                                            const char *name,
@@ -1247,12 +1306,18 @@ void        nm_platform_wifi_set_mode         (NMPlatform *self, int ifindex, NM
 void        nm_platform_wifi_set_powersave    (NMPlatform *self, int ifindex, guint32 powersave);
 guint32     nm_platform_wifi_find_frequency   (NMPlatform *self, int ifindex, const guint32 *freqs);
 void        nm_platform_wifi_indicate_addressing_running (NMPlatform *self, int ifindex, gboolean running);
+NMSettingWirelessWakeOnWLan nm_platform_wifi_get_wake_on_wlan (NMPlatform *self, int ifindex);
 gboolean    nm_platform_wifi_set_wake_on_wlan (NMPlatform *self, int ifindex, NMSettingWirelessWakeOnWLan wowl);
 
 guint32     nm_platform_mesh_get_channel      (NMPlatform *self, int ifindex);
 gboolean    nm_platform_mesh_set_channel      (NMPlatform *self, int ifindex, guint32 channel);
 gboolean    nm_platform_mesh_set_ssid         (NMPlatform *self, int ifindex, const guint8 *ssid, gsize len);
 
+guint16     nm_platform_wpan_get_pan_id       (NMPlatform *platform, int ifindex);
+gboolean    nm_platform_wpan_set_pan_id       (NMPlatform *platform, int ifindex, guint16 pan_id);
+guint16     nm_platform_wpan_get_short_addr   (NMPlatform *platform, int ifindex);
+gboolean    nm_platform_wpan_set_short_addr   (NMPlatform *platform, int ifindex, guint16 short_addr);
+
 void                   nm_platform_ip4_address_set_addr (NMPlatformIP4Address *addr, in_addr_t address, guint8 plen);
 const struct in6_addr *nm_platform_ip6_address_get_peer (const NMPlatformIP6Address *addr);
 
@@ -1266,6 +1331,10 @@ NMPlatformError nm_platform_link_ip6tnl_add (NMPlatform *self,
                                              const char *name,
                                              const NMPlatformLnkIp6Tnl *props,
                                              const NMPlatformLink **out_link);
+NMPlatformError nm_platform_link_ip6gre_add (NMPlatform *self,
+                                             const char *name,
+                                             const NMPlatformLnkIp6Tnl *props,
+                                             const NMPlatformLink **out_link);
 NMPlatformError nm_platform_link_ipip_add (NMPlatform *self,
                                            const char *name,
                                            const NMPlatformLnkIpIp *props,
@@ -1289,6 +1358,13 @@ NMPlatformError nm_platform_link_tun_add (NMPlatform *self,
                                           const NMPlatformLnkTun *props,
                                           const NMPlatformLink **out_link,
                                           int *out_fd);
+NMPlatformError nm_platform_link_6lowpan_add (NMPlatform *self,
+                                              const char *name,
+                                              int parent,
+                                              const NMPlatformLink **out_link);
+gboolean nm_platform_link_6lowpan_get_properties (NMPlatform *self,
+                                                  int ifindex,
+                                                  int *out_parent);
 
 const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address);
 
@@ -1375,12 +1451,14 @@ const char *nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *bu
 const char *nm_platform_lnk_tun_to_string (const NMPlatformLnkTun *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireGuard *lnk, char *buf, gsize len);
 const char *nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *buf, gsize len);
 const char *nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *buf, gsize len);
 const char *nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsize len);
 const char *nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsize len);
 const char *nm_platform_qdisc_to_string (const NMPlatformQdisc *qdisc, char *buf, gsize len);
 const char *nm_platform_tfilter_to_string (const NMPlatformTfilter *tfilter, char *buf, gsize len);
+const char *nm_platform_vf_to_string (const NMPlatformVF *vf, char *buf, gsize len);
 
 const char *nm_platform_vlan_qos_mapping_to_string (const char *name,
                                                     const NMVlanQosMapping *map,
@@ -1388,6 +1466,11 @@ const char *nm_platform_vlan_qos_mapping_to_string (const char *name,
                                                     char *buf,
                                                     gsize len);
 
+struct _NMPWireGuardPeer;
+const char *nm_platform_wireguard_peer_to_string (const struct _NMPWireGuardPeer *peer,
+                                                  char *buf,
+                                                  gsize len);
+
 int nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b);
 int nm_platform_lnk_gre_cmp (const NMPlatformLnkGre *a, const NMPlatformLnkGre *b);
 int nm_platform_lnk_infiniband_cmp (const NMPlatformLnkInfiniband *a, const NMPlatformLnkInfiniband *b);
@@ -1399,6 +1482,7 @@ int nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *
 int nm_platform_lnk_tun_cmp (const NMPlatformLnkTun *a, const NMPlatformLnkTun *b);
 int nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b);
 int nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b);
+int nm_platform_lnk_wireguard_cmp (const NMPlatformLnkWireGuard *a, const NMPlatformLnkWireGuard *b);
 int nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b);
 int nm_platform_ip6_address_cmp (const NMPlatformIP6Address *a, const NMPlatformIP6Address *b);
 
@@ -1435,6 +1519,7 @@ void nm_platform_lnk_sit_hash_update (const NMPlatformLnkSit *obj, NMHashState *
 void nm_platform_lnk_tun_hash_update (const NMPlatformLnkTun *obj, NMHashState *h);
 void nm_platform_lnk_vlan_hash_update (const NMPlatformLnkVlan *obj, NMHashState *h);
 void nm_platform_lnk_vxlan_hash_update (const NMPlatformLnkVxlan *obj, NMHashState *h);
+void nm_platform_lnk_wireguard_hash_update (const NMPlatformLnkWireGuard *obj, NMHashState *h);
 
 void nm_platform_qdisc_hash_update (const NMPlatformQdisc *obj, NMHashState *h);
 void nm_platform_tfilter_hash_update (const NMPlatformTfilter *obj, NMHashState *h);
@@ -1452,6 +1537,17 @@ int nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPla
 gboolean nm_platform_ethtool_set_wake_on_lan (NMPlatform *self, int ifindex, NMSettingWiredWakeOnLan wol, const char *wol_password);
 gboolean nm_platform_ethtool_set_link_settings (NMPlatform *self, int ifindex, gboolean autoneg, guint32 speed, NMPlatformLinkDuplexType duplex);
 gboolean nm_platform_ethtool_get_link_settings (NMPlatform *self, int ifindex, gboolean *out_autoneg, guint32 *out_speed, NMPlatformLinkDuplexType *out_duplex);
+
+typedef struct _NMEthtoolFeatureStates NMEthtoolFeatureStates;
+
+NMEthtoolFeatureStates *nm_platform_ethtool_get_link_features (NMPlatform *self,
+                                                               int ifindex);
+gboolean nm_platform_ethtool_set_features (NMPlatform *self,
+                                           int ifindex,
+                                           const NMEthtoolFeatureStates *features,
+                                           const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+                                           gboolean do_set /* or reset */);
+
 const char * nm_platform_link_duplex_type_to_string (NMPlatformLinkDuplexType duplex);
 
 void nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 50e6825b..fdc27440 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2015 - 2017 Red Hat, Inc.
+ * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -27,10 +27,14 @@
 #include <libudev.h>
 
 #include "nm-utils.h"
+#include "nm-utils/nm-secret-utils.h"
 
 #include "nm-core-utils.h"
 #include "nm-platform-utils.h"
 
+#include "wifi/nm-wifi-utils.h"
+#include "wpan/nm-wpan-utils.h"
+
 /*****************************************************************************/
 
 #define _NMLOG_DOMAIN LOGD_PLATFORM
@@ -337,12 +341,103 @@ _vlan_xgress_qos_mappings_cpy (guint *dst_n_map,
 		g_clear_pointer (dst_map, g_free);
 		*dst_n_map = src_n_map;
 		if (src_n_map > 0)
-			*dst_map = g_memdup (src_map, sizeof (*src_map) * src_n_map);
+			*dst_map = nm_memdup (src_map, sizeof (*src_map) * src_n_map);
 	}
 }
 
 /*****************************************************************************/
 
+static void
+_wireguard_allowed_ip_hash_update (const NMPWireGuardAllowedIP *ip,
+                                   NMHashState *h)
+{
+	nm_hash_update_vals (h, ip->family,
+	                        ip->mask);
+
+	if (ip->family == AF_INET)
+		nm_hash_update_val (h, ip->addr.addr4);
+	else if (ip->family == AF_INET6)
+		nm_hash_update_val (h, ip->addr.addr6);
+}
+
+static int
+_wireguard_allowed_ip_cmp (const NMPWireGuardAllowedIP *a,
+                           const NMPWireGuardAllowedIP *b)
+{
+	NM_CMP_SELF (a, b);
+
+	NM_CMP_FIELD (a, b, family);
+	NM_CMP_FIELD (a, b, mask);
+
+	if (a->family == AF_INET)
+		NM_CMP_FIELD (a, b, addr.addr4);
+	else if (a->family == AF_INET6)
+		NM_CMP_FIELD_IN6ADDR (a, b, addr.addr6);
+
+	return 0;
+}
+
+static void
+_wireguard_peer_hash_update (const NMPWireGuardPeer *peer,
+                             NMHashState *h)
+{
+	guint i;
+
+	nm_hash_update (h, peer->public_key, sizeof (peer->public_key));
+	nm_hash_update (h, peer->preshared_key, sizeof (peer->preshared_key));
+	nm_hash_update_vals (h,
+	                     peer->persistent_keepalive_interval,
+	                     peer->allowed_ips_len,
+	                     peer->rx_bytes,
+	                     peer->tx_bytes,
+	                     peer->last_handshake_time.tv_sec,
+	                     peer->last_handshake_time.tv_nsec,
+	                     peer->endpoint_port,
+	                     peer->endpoint_family);
+
+	if (peer->endpoint_family == AF_INET)
+		nm_hash_update_val (h, peer->endpoint_addr.addr4);
+	else if (peer->endpoint_family == AF_INET6)
+		nm_hash_update_val (h, peer->endpoint_addr.addr6);
+
+	for (i = 0; i < peer->allowed_ips_len; i++)
+		_wireguard_allowed_ip_hash_update (&peer->allowed_ips[i], h);
+}
+
+static int
+_wireguard_peer_cmp (const NMPWireGuardPeer *a,
+                     const NMPWireGuardPeer *b)
+{
+	guint i;
+
+	NM_CMP_SELF (a, b);
+
+	NM_CMP_FIELD (a, b, last_handshake_time.tv_sec);
+	NM_CMP_FIELD (a, b, last_handshake_time.tv_nsec);
+	NM_CMP_FIELD (a, b, rx_bytes);
+	NM_CMP_FIELD (a, b, tx_bytes);
+	NM_CMP_FIELD (a, b, allowed_ips_len);
+	NM_CMP_FIELD (a, b, persistent_keepalive_interval);
+	NM_CMP_FIELD (a, b, endpoint_port);
+	NM_CMP_FIELD (a, b, endpoint_family);
+	NM_CMP_FIELD_MEMCMP (a, b, public_key);
+	NM_CMP_FIELD_MEMCMP (a, b, preshared_key);
+
+	if (a->endpoint_family == AF_INET)
+		NM_CMP_FIELD (a, b, endpoint_addr.addr4);
+	else if (a->endpoint_family == AF_INET6)
+		NM_CMP_FIELD_IN6ADDR (a, b, endpoint_addr.addr6);
+
+	for (i = 0; i < a->allowed_ips_len; i++) {
+		NM_CMP_RETURN (_wireguard_allowed_ip_cmp (&a->allowed_ips[i],
+		                                          &b->allowed_ips[i]));
+	}
+
+	return 0;
+}
+
+/*****************************************************************************/
+
 static const char *
 _link_get_driver (struct udev_device *udevice, const char *kind, int ifindex)
 {
@@ -456,6 +551,7 @@ _vt_cmd_obj_dispose_link (NMPObject *obj)
 		udev_device_unref (obj->_link.udev.device);
 		obj->_link.udev.device = NULL;
 	}
+	g_clear_object (&obj->_link.ext_data);
 	nmp_object_unref (obj->_link.netlink.lnk);
 }
 
@@ -466,6 +562,28 @@ _vt_cmd_obj_dispose_lnk_vlan (NMPObject *obj)
 	g_free ((gpointer) obj->_lnk_vlan.egress_qos_map);
 }
 
+static void
+_wireguard_clear (NMPObjectLnkWireGuard *lnk)
+{
+	guint i;
+
+	nm_explicit_bzero (lnk->_public.private_key,
+	                   sizeof (lnk->_public.private_key));
+	for (i = 0; i < lnk->peers_len; i++) {
+		NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &lnk->peers[i];
+
+		nm_explicit_bzero (peer->preshared_key, sizeof (peer->preshared_key));
+	}
+	g_free ((gpointer) lnk->peers);
+	g_free ((gpointer) lnk->_allowed_ips_buf);
+}
+
+static void
+_vt_cmd_obj_dispose_lnk_wireguard (NMPObject *obj)
+{
+	_wireguard_clear (&obj->_lnk_wireguard);
+}
+
 static NMPObject *
 _nmp_object_new_from_class (const NMPClass *klass)
 {
@@ -631,31 +749,35 @@ static const char *
 _vt_cmd_obj_to_string_link (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size)
 {
 	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
-	char buf2[sizeof (_nm_utils_to_string_buffer)];
-	char buf3[sizeof (_nm_utils_to_string_buffer)];
+	char *b = buf;
 
 	switch (to_string_mode) {
 	case NMP_OBJECT_TO_STRING_ID:
 		return klass->cmd_plobj_to_string_id (&obj->object, buf, buf_size);
 	case NMP_OBJECT_TO_STRING_ALL:
-		g_snprintf (buf, buf_size,
-		            "[%s,%p,%u,%calive,%cvisible,%cin-nl,%p; %s]",
-		            klass->obj_type_name, obj, obj->parent._ref_count,
-		            nmp_object_is_alive (obj) ? '+' : '-',
-		            nmp_object_is_visible (obj) ? '+' : '-',
-		            obj->_link.netlink.is_in_netlink ? '+' : '-',
-		            obj->_link.udev.device,
-		            nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof (buf2)));
+		nm_utils_strbuf_append (&b, &buf_size,
+		                        "[%s,%p,%u,%calive,%cvisible,%cin-nl,%p; ",
+		                        klass->obj_type_name, obj, obj->parent._ref_count,
+		                        nmp_object_is_alive (obj) ? '+' : '-',
+		                        nmp_object_is_visible (obj) ? '+' : '-',
+		                        obj->_link.netlink.is_in_netlink ? '+' : '-',
+		                        obj->_link.udev.device);
+		NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, b, buf_size);
+		nm_utils_strbuf_seek_end (&b, &buf_size);
+		if (obj->_link.netlink.lnk) {
+			nm_utils_strbuf_append_str (&b, &buf_size, "; ");
+			nmp_object_to_string (obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_ALL, b, buf_size);
+			nm_utils_strbuf_seek_end (&b, &buf_size);
+		}
+		nm_utils_strbuf_append_c (&b, &buf_size, ']');
 		return buf;
 	case NMP_OBJECT_TO_STRING_PUBLIC:
+		NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, b, buf_size);
 		if (obj->_link.netlink.lnk) {
-			NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf2, sizeof (buf2));
-			nmp_object_to_string (obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_PUBLIC, buf3, sizeof (buf3));
-			g_snprintf (buf, buf_size,
-			            "%s; %s",
-			            buf2, buf3);
-		} else
-			NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf, buf_size);
+			nm_utils_strbuf_seek_end (&b, &buf_size);
+			nm_utils_strbuf_append_str (&b, &buf_size, "; ");
+			nmp_object_to_string (obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_PUBLIC, b, buf_size);
+		}
 		return buf;
 	default:
 		g_return_val_if_reached ("ERROR");
@@ -720,6 +842,55 @@ _vt_cmd_obj_to_string_lnk_vlan (const NMPObject *obj, NMPObjectToStringMode to_s
 	}
 }
 
+static const char *
+_vt_cmd_obj_to_string_lnk_wireguard (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size)
+{
+	const NMPClass *klass;
+	char buf2[sizeof (_nm_utils_to_string_buffer)];
+	char *b;
+	guint i;
+
+	klass = NMP_OBJECT_GET_CLASS (obj);
+
+	switch (to_string_mode) {
+	case NMP_OBJECT_TO_STRING_ID:
+		g_snprintf (buf, buf_size, "%p", obj);
+		return buf;
+	case NMP_OBJECT_TO_STRING_ALL:
+		b = buf;
+
+		nm_utils_strbuf_append (&b, &buf_size,
+		                        "[%s,%p,%u,%calive,%cvisible; %s"
+		                        "%s",
+		                        klass->obj_type_name, obj, obj->parent._ref_count,
+		                        nmp_object_is_alive (obj) ? '+' : '-',
+		                        nmp_object_is_visible (obj) ? '+' : '-',
+		                        nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof (buf2)),
+		                        obj->_lnk_wireguard.peers_len > 0
+		                          ? " peers {"
+		                          : "");
+
+		for (i = 0; i < obj->_lnk_wireguard.peers_len; i++) {
+			const NMPWireGuardPeer *peer = &obj->_lnk_wireguard.peers[i];
+
+			nm_utils_strbuf_append_str (&b, &buf_size, " { ");
+			nm_platform_wireguard_peer_to_string (peer, b, buf_size);
+			nm_utils_strbuf_seek_end (&b, &buf_size);
+			nm_utils_strbuf_append_str (&b, &buf_size, " }");
+		}
+		if (obj->_lnk_wireguard.peers_len)
+			nm_utils_strbuf_append_str (&b, &buf_size, " }");
+
+		return buf;
+	case NMP_OBJECT_TO_STRING_PUBLIC:
+		NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf, buf_size);
+
+		return buf;
+	default:
+		g_return_val_if_reached ("ERROR");
+	}
+}
+
 #define _vt_cmd_plobj_to_string_id(type, plat_type, ...) \
 static const char * \
 _vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, gsize buf_len) \
@@ -768,6 +939,7 @@ _vt_cmd_obj_hash_update_link (const NMPObject *obj, NMHashState *h)
 	nm_platform_link_hash_update (&obj->link, h);
 	nm_hash_update_vals (h,
 	                     obj->_link.netlink.is_in_netlink,
+	                     obj->_link.wireguard_family_id,
 	                     obj->_link.udev.device);
 	if (obj->_link.netlink.lnk)
 		nmp_object_hash_update (obj->_link.netlink.lnk, h);
@@ -787,17 +959,26 @@ _vt_cmd_obj_hash_update_lnk_vlan (const NMPObject *obj, NMHashState *h)
 	                                       h);
 }
 
+static void
+_vt_cmd_obj_hash_update_lnk_wireguard (const NMPObject *obj, NMHashState *h)
+{
+	guint i;
+
+	nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LNK_WIREGUARD);
+
+	nm_platform_lnk_wireguard_hash_update (&obj->lnk_wireguard, h);
+
+	nm_hash_update_val (h, obj->_lnk_wireguard.peers_len);
+	for (i = 0; i < obj->_lnk_wireguard.peers_len; i++)
+		_wireguard_peer_hash_update (&obj->_lnk_wireguard.peers[i], h);
+}
+
 int
 nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
 {
 	const NMPClass *klass1, *klass2;
 
-	if (obj1 == obj2)
-		return 0;
-	if (!obj1)
-		return -1;
-	if (!obj2)
-		return 1;
+	NM_CMP_SELF (obj1, obj2);
 
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), -1);
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), 1);
@@ -818,16 +999,11 @@ nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
 static int
 _vt_cmd_obj_cmp_link (const NMPObject *obj1, const NMPObject *obj2)
 {
-	int i;
+	NM_CMP_RETURN (nm_platform_link_cmp (&obj1->link, &obj2->link));
+	NM_CMP_DIRECT (obj1->_link.netlink.is_in_netlink, obj2->_link.netlink.is_in_netlink);
+	NM_CMP_RETURN (nmp_object_cmp (obj1->_link.netlink.lnk, obj2->_link.netlink.lnk));
+	NM_CMP_DIRECT (obj1->_link.wireguard_family_id, obj2->_link.wireguard_family_id);
 
-	i = nm_platform_link_cmp (&obj1->link, &obj2->link);
-	if (i)
-		return i;
-	if (obj1->_link.netlink.is_in_netlink != obj2->_link.netlink.is_in_netlink)
-		return obj1->_link.netlink.is_in_netlink ? -1 : 1;
-	i = nmp_object_cmp (obj1->_link.netlink.lnk, obj2->_link.netlink.lnk);
-	if (i)
-		return i;
 	if (obj1->_link.udev.device != obj2->_link.udev.device) {
 		if (!obj1->_link.udev.device)
 			return -1;
@@ -840,6 +1016,7 @@ _vt_cmd_obj_cmp_link (const NMPObject *obj1, const NMPObject *obj2)
 		 * Have this check as very last. */
 		return (obj1->_link.udev.device < obj2->_link.udev.device) ? -1 : 1;
 	}
+
 	return 0;
 }
 
@@ -865,6 +1042,21 @@ _vt_cmd_obj_cmp_lnk_vlan (const NMPObject *obj1, const NMPObject *obj2)
 	return c;
 }
 
+static int
+_vt_cmd_obj_cmp_lnk_wireguard (const NMPObject *obj1, const NMPObject *obj2)
+{
+	guint i;
+
+	NM_CMP_RETURN (nm_platform_lnk_wireguard_cmp (&obj1->lnk_wireguard, &obj2->lnk_wireguard));
+
+	NM_CMP_FIELD (obj1, obj2, _lnk_wireguard.peers_len);
+
+	for (i = 0; i < obj1->_lnk_wireguard.peers_len; i++)
+		NM_CMP_RETURN (_wireguard_peer_cmp (&obj1->_lnk_wireguard.peers[i], &obj2->_lnk_wireguard.peers[i]));
+
+	return 0;
+}
+
 /* @src is a const object, which is not entirely correct for link types, where
  * we increase the ref count for src->_link.udev.device.
  * Hence, nmp_object_copy() can violate the const promise of @src.
@@ -908,6 +1100,12 @@ _vt_cmd_obj_copy_link (NMPObject *dst, const NMPObject *src)
 			nmp_object_unref (dst->_link.netlink.lnk);
 		dst->_link.netlink.lnk = src->_link.netlink.lnk;
 	}
+	if (dst->_link.ext_data != src->_link.ext_data) {
+		if (dst->_link.ext_data)
+			g_clear_object (&dst->_link.ext_data);
+		if (src->_link.ext_data)
+			dst->_link.ext_data = g_object_ref (src->_link.ext_data);
+	}
 	dst->_link = src->_link;
 }
 
@@ -925,6 +1123,41 @@ _vt_cmd_obj_copy_lnk_vlan (NMPObject *dst, const NMPObject *src)
 	                               src->_lnk_vlan.egress_qos_map);
 }
 
+static void
+_vt_cmd_obj_copy_lnk_wireguard (NMPObject *dst, const NMPObject *src)
+{
+	guint i;
+
+	nm_assert (dst != src);
+
+	_wireguard_clear (&dst->_lnk_wireguard);
+
+	dst->_lnk_wireguard = src->_lnk_wireguard;
+
+	dst->_lnk_wireguard.peers = nm_memdup (dst->_lnk_wireguard.peers,
+	                                       sizeof (NMPWireGuardPeer) * dst->_lnk_wireguard.peers_len);
+	dst->_lnk_wireguard._allowed_ips_buf = nm_memdup (dst->_lnk_wireguard._allowed_ips_buf,
+	                                                  sizeof (NMPWireGuardAllowedIP) * dst->_lnk_wireguard._allowed_ips_buf_len);
+
+	/* all the peers' pointers point into the buffer. They need to be readjusted. */
+	for (i = 0; i < dst->_lnk_wireguard.peers_len; i++) {
+		NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &dst->_lnk_wireguard.peers[i];
+
+		if (peer->allowed_ips_len == 0) {
+			nm_assert (!peer->allowed_ips);
+			continue;
+		}
+		nm_assert (dst->_lnk_wireguard._allowed_ips_buf_len > 0);
+		nm_assert (src->_lnk_wireguard._allowed_ips_buf);
+		nm_assert (peer->allowed_ips >= src->_lnk_wireguard._allowed_ips_buf);
+		nm_assert (&peer->allowed_ips[peer->allowed_ips_len] <= &src->_lnk_wireguard._allowed_ips_buf[src->_lnk_wireguard._allowed_ips_buf_len]);
+
+		peer->allowed_ips = &dst->_lnk_wireguard._allowed_ips_buf[peer->allowed_ips - src->_lnk_wireguard._allowed_ips_buf];
+	}
+
+	nm_assert (nmp_object_equal (src, dst));
+}
+
 #define _vt_cmd_plobj_id_copy(type, plat_type, cmd) \
 static void \
 _vt_cmd_plobj_id_copy_##type (NMPlatformObject *_dst, const NMPlatformObject *_src) \
@@ -2702,6 +2935,17 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_gre_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_gre_cmp,
 	},
+	[NMP_OBJECT_TYPE_LNK_GRETAP - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_GRETAP,
+		.sizeof_data                        = sizeof (NMPObjectLnkGre),
+		.sizeof_public                      = sizeof (NMPlatformLnkGre),
+		.obj_type_name                      = "gretap",
+		.lnk_link_type                      = NM_LINK_TYPE_GRETAP,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_gre_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_gre_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_gre_cmp,
+	},
 	[NMP_OBJECT_TYPE_LNK_INFINIBAND - 1] = {
 		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_INFINIBAND,
@@ -2724,6 +2968,28 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_ip6tnl_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ip6tnl_cmp,
 	},
+	[NMP_OBJECT_TYPE_LNK_IP6GRE - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_IP6GRE,
+		.sizeof_data                        = sizeof (NMPObjectLnkIp6Tnl),
+		.sizeof_public                      = sizeof (NMPlatformLnkIp6Tnl),
+		.obj_type_name                      = "ip6gre",
+		.lnk_link_type                      = NM_LINK_TYPE_IP6GRE,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_ip6tnl_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_ip6tnl_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ip6tnl_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_IP6GRETAP - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_IP6GRETAP,
+		.sizeof_data                        = sizeof (NMPObjectLnkIp6Tnl),
+		.sizeof_public                      = sizeof (NMPlatformLnkIp6Tnl),
+		.obj_type_name                      = "ip6gretap",
+		.lnk_link_type                      = NM_LINK_TYPE_IP6GRETAP,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_ip6tnl_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_ip6tnl_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ip6tnl_cmp,
+	},
 	[NMP_OBJECT_TYPE_LNK_IPIP - 1] = {
 		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_IPIP,
@@ -2817,5 +3083,20 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_vxlan_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vxlan_cmp,
 	},
+	[NMP_OBJECT_TYPE_LNK_WIREGUARD - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_WIREGUARD,
+		.sizeof_data                        = sizeof (NMPObjectLnkWireGuard),
+		.sizeof_public                      = sizeof (NMPlatformLnkWireGuard),
+		.obj_type_name                      = "wireguard",
+		.lnk_link_type                      = NM_LINK_TYPE_WIREGUARD,
+		.cmd_obj_hash_update                = _vt_cmd_obj_hash_update_lnk_wireguard,
+		.cmd_obj_cmp                        = _vt_cmd_obj_cmp_lnk_wireguard,
+		.cmd_obj_copy                       = _vt_cmd_obj_copy_lnk_wireguard,
+		.cmd_obj_dispose                    = _vt_cmd_obj_dispose_lnk_wireguard,
+		.cmd_obj_to_string                  = _vt_cmd_obj_to_string_lnk_wireguard,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_wireguard_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_vlan_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vlan_cmp,
+	},
 };
-
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index f473f462..97be8321 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2015 - 2017 Red Hat, Inc.
+ * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
 
 #ifndef __NMP_OBJECT_H__
@@ -27,6 +27,36 @@
 
 struct udev_device;
 
+/*****************************************************************************/
+
+typedef struct {
+	NMIPAddr addr;
+	guint8 family;
+	guint8 mask;
+} NMPWireGuardAllowedIP;
+
+typedef struct _NMPWireGuardPeer {
+	NMIPAddr endpoint_addr;
+	struct timespec last_handshake_time;
+	guint64 rx_bytes;
+	guint64 tx_bytes;
+	union {
+		const NMPWireGuardAllowedIP *allowed_ips;
+		guint _construct_idx_start;
+	};
+	union {
+		guint allowed_ips_len;
+		guint _construct_idx_end;
+	};
+	guint16 persistent_keepalive_interval;
+	guint16 endpoint_port;
+	guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN];
+	guint8 preshared_key[NMP_WIREGUARD_SYMMETRIC_KEY_LEN];
+	guint8 endpoint_family;
+} NMPWireGuardPeer;
+
+/*****************************************************************************/
+
 typedef enum { /*< skip >*/
 	NMP_OBJECT_TO_STRING_ID,
 	NMP_OBJECT_TO_STRING_PUBLIC,
@@ -164,6 +194,14 @@ typedef struct {
 		 */
 		struct udev_device *device;
 	} udev;
+
+	/* Auxiliary data object for Wi-Fi and WPAN */
+	GObject *ext_data;
+
+	/* FIXME: not every NMPObjectLink should pay the price for tracking
+	 * the wireguard family id. This should be tracked via ext_data, which
+	 * would be exactly the right place. */
+	int wireguard_family_id;
 } NMPObjectLink;
 
 typedef struct {
@@ -214,6 +252,14 @@ typedef struct {
 } NMPObjectLnkVxlan;
 
 typedef struct {
+	NMPlatformLnkWireGuard _public;
+	const NMPWireGuardPeer *peers;
+	const NMPWireGuardAllowedIP *_allowed_ips_buf;
+	guint peers_len;
+	guint _allowed_ips_buf_len;
+} NMPObjectLnkWireGuard;
+
+typedef struct {
 	NMPlatformIP4Address _public;
 } NMPObjectIP4Address;
 
@@ -278,6 +324,9 @@ struct _NMPObject {
 		NMPlatformLnkVxlan      lnk_vxlan;
 		NMPObjectLnkVxlan       _lnk_vxlan;
 
+		NMPlatformLnkWireGuard  lnk_wireguard;
+		NMPObjectLnkWireGuard   _lnk_wireguard;
+
 		NMPlatformIPAddress     ip_address;
 		NMPlatformIPXAddress    ipx_address;
 		NMPlatformIP4Address    ip4_address;
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 8b8c87d8..12d91812 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -60,7 +60,7 @@ test_cleanup_internal (void)
 	g_assert (ifindex > 0);
 
 	/* wait for kernel to add the IPv6 link local address... it takes a bit. */
-	NMTST_WAIT_ASSERT (100, {
+	NMTST_WAIT_ASSERT (300, {
 		gs_unref_array GArray *addrs = NULL;
 		const NMPlatformIP6Address *a;
 
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 42569d5b..3dedbd10 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -1243,21 +1243,29 @@ nmtstp_link_gre_add (NMPlatform *platform,
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
 	char buffer[INET_ADDRSTRLEN];
+	NMLinkType link_type;
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
 
 	external_command = nmtstp_run_command_check_external (external_command);
+	link_type = lnk->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE;
 
 	_init_platform (&platform, external_command);
 
 	if (external_command) {
 		gs_free char *dev = NULL;
+		char *obj, *type;
 
 		if (lnk->parent_ifindex)
 			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (platform, lnk->parent_ifindex));
 
-		success = !nmtstp_run_command ("ip tunnel add %s mode gre %s local %s remote %s ttl %u tos %02x %s",
+		obj = lnk->is_tap ? "link" : "tunnel";
+		type = lnk->is_tap ? "type gretap" : "mode gre";
+
+		success = !nmtstp_run_command ("ip %s add %s %s %s local %s remote %s ttl %u tos %02x %s",
+		                                obj,
 		                                name,
+		                                type,
 		                                dev ?: "",
 		                                nm_utils_inet4_ntop (lnk->local, NULL),
 		                                nm_utils_inet4_ntop (lnk->remote, buffer),
@@ -1265,11 +1273,11 @@ nmtstp_link_gre_add (NMPlatform *platform,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
-			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_GRE, 100);
+			pllink = nmtstp_assert_wait_for_link (platform, name, link_type, 100);
 	} else
 		success = nm_platform_link_gre_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
 
-	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_GRE);
+	_assert_pllink (platform, success, pllink, name, link_type);
 
 	return pllink;
 }
@@ -1289,6 +1297,7 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 	gboolean tclass_inherit;
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
+	g_assert (!lnk->is_gre);
 
 	external_command = nmtstp_run_command_check_external (external_command);
 
@@ -1336,6 +1345,56 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 }
 
 const NMPlatformLink *
+nmtstp_link_ip6gre_add (NMPlatform *platform,
+                        gboolean external_command,
+                        const char *name,
+                        const NMPlatformLnkIp6Tnl *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+	char buffer[INET6_ADDRSTRLEN];
+	char tclass[20];
+	gboolean tclass_inherit;
+
+	g_assert (nm_utils_is_valid_iface_name (name, NULL));
+	g_assert (lnk->is_gre);
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	_init_platform (&platform, external_command);
+
+	if (external_command) {
+		gs_free char *dev = NULL;
+
+		if (lnk->parent_ifindex)
+			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (platform, lnk->parent_ifindex));
+
+		tclass_inherit = NM_FLAGS_HAS (lnk->flags, IP6_TNL_F_USE_ORIG_TCLASS);
+
+		success = !nmtstp_run_command ("ip link add %s type %s %s local %s remote %s ttl %u tclass %s flowlabel %x",
+		                                name,
+		                                lnk->is_tap ? "ip6gretap" : "ip6gre",
+		                                dev,
+		                                nm_utils_inet6_ntop (&lnk->local, NULL),
+		                                nm_utils_inet6_ntop (&lnk->remote, buffer),
+		                                lnk->ttl,
+		                                tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass),
+		                                lnk->flow_label);
+		if (success) {
+			pllink = nmtstp_assert_wait_for_link (platform,
+			                                      name,
+			                                      lnk->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE,
+			                                      100);
+		}
+	} else
+		success = nm_platform_link_ip6gre_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	_assert_pllink (platform, success, pllink, name, lnk->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE);
+
+	return pllink;
+}
+
+const NMPlatformLink *
 nmtstp_link_ipip_add (NMPlatform *platform,
                       gboolean external_command,
                       const char *name,
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index bd02b0d7..1baadfa1 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -80,7 +80,7 @@ typedef struct {
 	gulong handler_id;
 	const char *name;
 	NMPlatformSignalChangeType change_type;
-	gint received_count;
+	int received_count;
 	GMainLoop *loop;
 	int ifindex;
 	const char *ifname;
@@ -297,6 +297,10 @@ const NMPlatformLink *nmtstp_link_ip6tnl_add (NMPlatform *platform,
                                               gboolean external_command,
                                               const char *name,
                                               const NMPlatformLnkIp6Tnl *lnk);
+const NMPlatformLink *nmtstp_link_ip6gre_add (NMPlatform *platform,
+                                              gboolean external_command,
+                                              const char *name,
+                                              const NMPlatformLnkIp6Tnl *lnk);
 const NMPlatformLink *nmtstp_link_ipip_add (NMPlatform *platform,
                                             gboolean external_command,
                                             const char *name,
diff --git a/src/platform/tests/test-general.c b/src/platform/tests/test-general.c
index 8708c80f..eebc15d8 100644
--- a/src/platform/tests/test-general.c
+++ b/src/platform/tests/test-general.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2015 Red Hat, Inc.
+ * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -57,7 +57,7 @@ NMTST_DEFINE ();
 int
 main (int argc, char **argv)
 {
-	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
+	nmtst_init_assert_logging (&argc, &argv, "WARN", "DEFAULT");
 
 	g_test_add_func ("/general/init_linux_platform", test_init_linux_platform);
 	g_test_add_func ("/general/link_get_all", test_link_get_all);
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index cd5880e3..057490c4 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -25,6 +25,7 @@
 #include <sys/types.h>
 #include <linux/if_tun.h>
 
+#include "nm-utils/nm-io-utils.h"
 #include "platform/nmp-object.h"
 #include "platform/nmp-netns.h"
 #include "platform/nm-platform-utils.h"
@@ -699,6 +700,7 @@ test_software_detect (gconstpointer user_data)
 	guint i_step;
 	const gboolean ext = test_data->external_command;
 	NMPlatformLnkTun lnk_tun;
+	NMPlatformLnkGre lnk_gre = { };
 	nm_auto_close int tun_fd = -1;
 
 	nmtstp_run_command_check ("ip link add %s type dummy", PARENT_NAME);
@@ -706,7 +708,6 @@ test_software_detect (gconstpointer user_data)
 
 	switch (test_data->link_type) {
 	case NM_LINK_TYPE_GRE: {
-		NMPlatformLnkGre lnk_gre = { };
 		gboolean gracefully_skip = FALSE;
 
 		lnk_gre.local = nmtst_inet4_from_string ("192.168.233.204");
@@ -730,6 +731,31 @@ test_software_detect (gconstpointer user_data)
 		}
 		break;
 	}
+	case NM_LINK_TYPE_GRETAP: {
+		gboolean gracefully_skip = FALSE;
+
+		lnk_gre.local = nmtst_inet4_from_string ("192.168.1.133");
+		lnk_gre.remote = nmtst_inet4_from_string ("172.168.101.2");
+		lnk_gre.parent_ifindex = ifindex_parent;
+		lnk_gre.ttl = 39;
+		lnk_gre.tos = 12;
+		lnk_gre.path_mtu_discovery = FALSE;
+		lnk_gre.is_tap = TRUE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "gretap0")) {
+			/* Seems that the ip_gre module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip_gre", NULL) != 0;
+		}
+
+		if (!nmtstp_link_gre_add (NULL, ext, DEVICE_NAME, &lnk_gre)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create gretap tunnel because of missing ip_gre module (modprobe ip_gre)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding GRETAP tunnel");
+		}
+		break;
+	}
 	case NM_LINK_TYPE_IPIP: {
 		NMPlatformLnkIpIp lnk_ipip = { };
 		gboolean gracefully_skip = FALSE;
@@ -794,6 +820,58 @@ test_software_detect (gconstpointer user_data)
 		}
 		break;
 	}
+	case NM_LINK_TYPE_IP6GRE: {
+		NMPlatformLnkIp6Tnl lnk_ip6tnl = { };
+		gboolean gracefully_skip = FALSE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "ip6gre0")) {
+			/* Seems that the ip6_tunnel module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip6_gre", NULL) != 0;
+		}
+
+		lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::42");
+		lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::aaaa");
+		lnk_ip6tnl.parent_ifindex = ifindex_parent;
+		lnk_ip6tnl.tclass = 21;
+		lnk_ip6tnl.flow_label = 1338;
+		lnk_ip6tnl.is_gre = TRUE;
+
+		if (!nmtstp_link_ip6gre_add (NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create ip6gre tunnel because of missing ip6_gre module (modprobe ip6_gre)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding IP6GRE tunnel");
+		}
+		break;
+	}
+	case NM_LINK_TYPE_IP6GRETAP: {
+		NMPlatformLnkIp6Tnl lnk_ip6tnl = { };
+		gboolean gracefully_skip = FALSE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "ip6gre0")) {
+			/* Seems that the ip6_tunnel module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip6_gre", NULL) != 0;
+		}
+
+		lnk_ip6tnl.local = *nmtst_inet6_from_string ("fe80::abcd");
+		lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fc01::bbbb");
+		lnk_ip6tnl.parent_ifindex = ifindex_parent;
+		lnk_ip6tnl.ttl = 10;
+		lnk_ip6tnl.tclass = 22;
+		lnk_ip6tnl.flow_label = 1339;
+		lnk_ip6tnl.is_gre = TRUE;
+		lnk_ip6tnl.is_tap = TRUE;
+
+		if (!nmtstp_link_ip6gre_add (NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create ip6gretap tunnel because of missing ip6_gre module (modprobe ip6_gre)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding IP6GRETAP tunnel");
+		}
+		break;
+	}
 	case NM_LINK_TYPE_MACVLAN: {
 		NMPlatformLnkMacvlan lnk_macvlan = { };
 		const NMPlatformLink *dummy;
@@ -970,16 +1048,15 @@ test_software_detect (gconstpointer user_data)
 			const NMPlatformLnkGre *plnk = &lnk->lnk_gre;
 
 			g_assert (plnk == nm_platform_link_get_lnk_gre (NM_PLATFORM_GET, ifindex, NULL));
-			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
-			g_assert_cmpint (plnk->input_flags, ==, 0);
-			g_assert_cmpint (plnk->output_flags, ==, 0);
-			g_assert_cmpint (plnk->input_key, ==, 0);
-			g_assert_cmpint (plnk->output_key, ==, 0);
-			nmtst_assert_ip4_address (plnk->local, "192.168.233.204");
-			nmtst_assert_ip4_address (plnk->remote, "172.168.10.25");
-			g_assert_cmpint (plnk->ttl, ==, 174);
-			g_assert_cmpint (plnk->tos, ==, 37);
-			g_assert_cmpint (plnk->path_mtu_discovery, ==, TRUE);
+			g_assert (nm_platform_lnk_gre_cmp (plnk, &lnk_gre) == 0);
+
+			break;
+		}
+		case NM_LINK_TYPE_GRETAP: {
+			const NMPlatformLnkGre *plnk = &lnk->lnk_gre;
+
+			g_assert (plnk == nm_platform_link_get_lnk_gretap (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert (nm_platform_lnk_gre_cmp (plnk, &lnk_gre) == 0);
 			break;
 		}
 		case NM_LINK_TYPE_IP6TNL: {
@@ -1012,6 +1089,33 @@ test_software_detect (gconstpointer user_data)
 			}
 			break;
 		}
+		case NM_LINK_TYPE_IP6GRE: {
+			const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl;
+
+			g_assert (plnk == nm_platform_link_get_lnk_ip6gre (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			nmtst_assert_ip6_address (&plnk->local, "fd01::42");
+			nmtst_assert_ip6_address (&plnk->remote, "fd01::aaaa");
+			g_assert_cmpint (plnk->tclass, ==, 21);
+			g_assert_cmpint (plnk->flow_label, ==, 1338);
+			g_assert_cmpint (plnk->is_gre, ==, TRUE);
+			g_assert_cmpint (plnk->is_tap, ==, FALSE);
+			break;
+		}
+		case NM_LINK_TYPE_IP6GRETAP: {
+			const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl;
+
+			g_assert (plnk == nm_platform_link_get_lnk_ip6gretap (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			nmtst_assert_ip6_address (&plnk->local, "fe80::abcd");
+			nmtst_assert_ip6_address (&plnk->remote, "fc01::bbbb");
+			g_assert_cmpint (plnk->ttl, ==, 10);
+			g_assert_cmpint (plnk->tclass, ==, 22);
+			g_assert_cmpint (plnk->flow_label, ==, 1339);
+			g_assert_cmpint (plnk->is_gre, ==, TRUE);
+			g_assert_cmpint (plnk->is_tap, ==, TRUE);
+			break;
+		}
 		case NM_LINK_TYPE_IPIP: {
 			const NMPlatformLnkIpIp *plnk = &lnk->lnk_ipip;
 
@@ -1732,7 +1836,7 @@ test_create_many_links_do (guint n_devices)
 	char name[64];
 	const NMPlatformLink *pllink;
 	gs_unref_array GArray *ifindexes = g_array_sized_new (FALSE, FALSE, sizeof (int), n_devices);
-	const gint EX = ((int) (nmtst_get_rand_int () % 4)) - 1;
+	const int EX = ((int) (nmtst_get_rand_int () % 4)) - 1;
 
 	g_assert (EX >= -1 && EX <= 2);
 
@@ -2519,7 +2623,9 @@ test_sysctl_rename (void)
 	case 0: {
 		gs_free char *c = NULL;
 
-		if (nm_utils_file_get_contents (dirfd, "ifindex", 1*1024*1024, &c, NULL, NULL) < 0)
+		if (nm_utils_file_get_contents (dirfd, "ifindex", 1*1024*1024,
+		                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+		                                &c, NULL, NULL) < 0)
 			g_assert_not_reached();
 		g_assert_cmpint (ifindex[0], ==, (int) _nm_utils_ascii_str_to_int64 (c, 10, 0, G_MAXINT, -1));
 		break;
@@ -2583,7 +2689,9 @@ test_sysctl_netns_switch (void)
 	{
 		gs_free char *c = NULL;
 
-		if (nm_utils_file_get_contents (dirfd, "ifindex", 0, &c, NULL, NULL) < 0)
+		if (nm_utils_file_get_contents (dirfd, "ifindex", 0,
+		                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+		                                &c, NULL, NULL) < 0)
 			g_assert_not_reached();
 		g_assert_cmpint (ifindex, ==, (int) _nm_utils_ascii_str_to_int64 (c, 10, 0, G_MAXINT, -1));
 	}
@@ -2595,7 +2703,11 @@ test_sysctl_netns_switch (void)
 	{
 		gs_free char *c = NULL;
 
-		if (nm_utils_file_get_contents (-1, nm_sprintf_bufa (100, "/sys/class/net/%s/ifindex", IFNAME), 0, &c, NULL, NULL) < 0)
+		if (nm_utils_file_get_contents (-1,
+		                                nm_sprintf_bufa (100, "/sys/class/net/%s/ifindex", IFNAME),
+		                                0,
+		                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+		                                &c, NULL, NULL) < 0)
 			ifindex_tmp = -1;
 		else
 			ifindex_tmp = _nm_utils_ascii_str_to_int64 (c, 10, 0, G_MAXINT, -2);
@@ -2610,6 +2722,102 @@ test_sysctl_netns_switch (void)
 
 /*****************************************************************************/
 
+static void
+ethtool_features_dump (const NMEthtoolFeatureStates *features)
+{
+	guint i, j;
+
+	g_assert (features);
+
+	_LOGT (">>> %u features (%u ss-features)", features->n_states, features->n_ss_features);
+
+	for (i = 0; i < features->n_states; i++) {
+		const NMEthtoolFeatureState *s = &features->states_list[i];
+
+		_LOGT (">>> feature-list[%3u]: %3d = %-32s (%3u) | %s %s %s %s",
+		       i,
+		       (int) s->info->ethtool_id,
+		       s->info->kernel_names[s->idx_kernel_name],
+		       s->idx_ss_features,
+		       s->active ? "ACT" : "act",
+		       s->available ? "AVA" : "ava",
+		       s->never_changed ? "NCH" : "nch",
+		       s->requested ? "REQ" : "req");
+	}
+	for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) {
+		_LOGT (">>> feature-idx [%3u]: %-32s = %u features",
+		       i + (guint) _NM_ETHTOOL_ID_FEATURE_FIRST,
+		       nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
+		       (guint) NM_PTRARRAY_LEN (features->states_indexed[i]));
+		for (j = 0; features->states_indexed[i] && features->states_indexed[i][j]; j++) {
+			const NMEthtoolFeatureState *s = features->states_indexed[i][j];
+
+			_LOGT (">>>  %3u: %-32s | %s %s %s %s",
+			       j,
+			       s->info->kernel_names[s->idx_kernel_name],
+			       s->active ? "ACT" : "act",
+			       s->available ? "AVA" : "ava",
+			       s->never_changed ? "NCH" : "nch",
+			       s->requested ? "REQ" : "req");
+		}
+	}
+}
+
+static void
+test_ethtool_features_get (void)
+{
+	gs_unref_ptrarray GPtrArray *gfree_keeper = g_ptr_array_new_with_free_func (g_free);
+	const int IFINDEX = 1;
+	guint i;
+	guint i_run;
+
+	for (i_run = 0; i_run < 5; i_run++) {
+		NMEthtoolFeatureStates *features;
+		NMTernary *requested;
+		gboolean do_set = TRUE;
+
+		requested = g_new (NMTernary, _NM_ETHTOOL_ID_FEATURE_NUM);
+		for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++)
+			requested[i] = NM_TERNARY_DEFAULT;
+		g_ptr_array_add (gfree_keeper, requested);
+
+		if (i_run == 0) {
+			requested[NM_ETHTOOL_ID_FEATURE_RX]                    = NM_TERNARY_FALSE;
+			requested[NM_ETHTOOL_ID_FEATURE_TSO]                   = NM_TERNARY_FALSE;
+			requested[NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION]  = NM_TERNARY_FALSE;
+		} else if (i_run == 1)
+			do_set = FALSE;
+		else if (i_run == 2) {
+			requested[NM_ETHTOOL_ID_FEATURE_TSO]                   = NM_TERNARY_FALSE;
+			requested[NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION]  = NM_TERNARY_TRUE;
+		} else if (i_run == 3)
+			do_set = FALSE;
+
+		_LOGT (">>> ethtool-features-get RUN %u (do-set=%s", i_run, do_set ? "set" : "reset");
+
+		features = nmp_utils_ethtool_get_features (IFINDEX);
+		g_ptr_array_add (gfree_keeper, features);
+
+		ethtool_features_dump (features);
+
+		if (_LOGT_ENABLED ()) {
+			int ignore;
+
+			ignore = system ("ethtool -k lo");
+			(void) ignore;
+		}
+
+		if (!do_set) {
+			requested = gfree_keeper->pdata[i_run * 2 - 2];
+			features = gfree_keeper->pdata[i_run * 2 - 1];
+		}
+
+		nmp_utils_ethtool_set_features (IFINDEX, features, requested, do_set);
+	}
+}
+
+/*****************************************************************************/
+
 NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP;
 
 void
@@ -2641,8 +2849,11 @@ _nmtstp_setup_tests (void)
 		g_test_add_func ("/link/external", test_external);
 
 		test_software_detect_add ("/link/software/detect/gre", NM_LINK_TYPE_GRE, 0);
+		test_software_detect_add ("/link/software/detect/gretap", NM_LINK_TYPE_GRETAP, 0);
 		test_software_detect_add ("/link/software/detect/ip6tnl/0", NM_LINK_TYPE_IP6TNL, 0);
 		test_software_detect_add ("/link/software/detect/ip6tnl/1", NM_LINK_TYPE_IP6TNL, 1);
+		test_software_detect_add ("/link/software/detect/ip6gre", NM_LINK_TYPE_IP6GRE, 0);
+		test_software_detect_add ("/link/software/detect/ip6gretap", NM_LINK_TYPE_IP6GRETAP, 0);
 		test_software_detect_add ("/link/software/detect/ipip", NM_LINK_TYPE_IPIP, 0);
 		test_software_detect_add ("/link/software/detect/macvlan", NM_LINK_TYPE_MACVLAN, 0);
 		test_software_detect_add ("/link/software/detect/macvtap", NM_LINK_TYPE_MACVTAP, 0);
@@ -2668,5 +2879,7 @@ _nmtstp_setup_tests (void)
 
 		g_test_add_func ("/general/sysctl/rename", test_sysctl_rename);
 		g_test_add_func ("/general/sysctl/netns-switch", test_sysctl_netns_switch);
+
+		g_test_add_func ("/link/ethtool/features/get", test_ethtool_features_get);
 	}
 }
diff --git a/src/platform/wifi/wifi-utils-nl80211.c b/src/platform/wifi/nm-wifi-utils-nl80211.c
index 6c2ff3f5..b3bb2bb6 100644
--- a/src/platform/wifi/wifi-utils-nl80211.c
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.c
@@ -15,14 +15,14 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2005 - 2011 Red Hat, Inc.
+ * Copyright (C) 2005 - 2018 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  * Copyright (C) 2011 Intel Corporation. All rights reserved.
  */
 
 #include "nm-default.h"
 
-#include "wifi-utils-nl80211.h"
+#include "nm-wifi-utils-nl80211.h"
 
 #include <errno.h>
 #include <string.h>
@@ -32,7 +32,7 @@
 #include <linux/nl80211.h>
 
 #include "platform/nm-netlink.h"
-#include "wifi-utils-private.h"
+#include "nm-wifi-utils-private.h"
 #include "platform/nm-platform.h"
 #include "platform/nm-platform-utils.h"
 #include "nm-utils.h"
@@ -47,14 +47,20 @@
 	} G_STMT_END
 
 typedef struct {
-	WifiData parent;
+	NMWifiUtils parent;
 	struct nl_sock *nl_sock;
 	guint32 *freqs;
 	int id;
 	int num_freqs;
 	int phy;
 	bool can_wowlan:1;
-} WifiDataNl80211;
+} NMWifiUtilsNl80211;
+
+typedef struct {
+	NMWifiUtilsClass parent;
+} NMWifiUtilsNl80211Class;
+
+G_DEFINE_TYPE (NMWifiUtilsNl80211, nm_wifi_utils_nl80211, NM_TYPE_WIFI_UTILS)
 
 static int
 ack_handler (struct nl_msg *msg, void *arg)
@@ -97,7 +103,7 @@ nla_put_failure:
 }
 
 static struct nl_msg *
-nl80211_alloc_msg (WifiDataNl80211 *nl80211, guint32 cmd, guint32 flags)
+nl80211_alloc_msg (NMWifiUtilsNl80211 *nl80211, guint32 cmd, guint32 flags)
 {
 	return _nl80211_alloc_msg (nl80211->id, nl80211->parent.ifindex, nl80211->phy, cmd, flags);
 }
@@ -154,7 +160,7 @@ _nl80211_send_and_recv (struct nl_sock *nl_sock,
 }
 
 static int
-nl80211_send_and_recv (WifiDataNl80211 *nl80211,
+nl80211_send_and_recv (NMWifiUtilsNl80211 *nl80211,
                        struct nl_msg *msg,
                        int (*valid_handler) (struct nl_msg *, void *),
                        void *valid_data)
@@ -164,13 +170,11 @@ nl80211_send_and_recv (WifiDataNl80211 *nl80211,
 }
 
 static void
-wifi_nl80211_deinit (WifiData *parent)
+dispose (GObject *object)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) parent;
+	NMWifiUtilsNl80211 *nl80211 = NM_WIFI_UTILS_NL80211 (object);
 
-	if (nl80211->nl_sock)
-		nl_socket_free (nl80211->nl_sock);
-	g_free (nl80211->freqs);
+	g_clear_pointer (&nl80211->freqs, g_free);
 }
 
 struct nl80211_iface_info {
@@ -207,9 +211,9 @@ nl80211_iface_info_handler (struct nl_msg *msg, void *arg)
 }
 
 static NM80211Mode
-wifi_nl80211_get_mode (WifiData *data)
+wifi_nl80211_get_mode (NMWifiUtils *data)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_iface_info iface_info = {
 		.mode = NM_802_11_MODE_UNKNOWN,
 	};
@@ -225,9 +229,9 @@ wifi_nl80211_get_mode (WifiData *data)
 }
 
 static gboolean
-wifi_nl80211_set_mode (WifiData *data, const NM80211Mode mode)
+wifi_nl80211_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
@@ -255,9 +259,9 @@ nla_put_failure:
 }
 
 static gboolean
-wifi_nl80211_set_powersave (WifiData *data, guint32 powersave)
+wifi_nl80211_set_powersave (NMWifiUtils *data, guint32 powersave)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
@@ -271,10 +275,64 @@ nla_put_failure:
 	return FALSE;
 }
 
+static int
+nl80211_get_wake_on_wlan_handler (struct nl_msg *msg, void *arg)
+{
+	NMSettingWirelessWakeOnWLan *wowl = arg;
+	struct nlattr *attrs[NL80211_ATTR_MAX + 1];
+	struct nlattr *trig[NUM_NL80211_WOWLAN_TRIG];
+	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
+
+	nla_parse (attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+	           genlmsg_attrlen(gnlh, 0), NULL);
+
+	if (!attrs[NL80211_ATTR_WOWLAN_TRIGGERS])
+		return NL_SKIP;
+
+	nla_parse (trig, MAX_NL80211_WOWLAN_TRIG,
+	           nla_data (attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
+	           nla_len (attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
+	           NULL);
+
+	*wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE;
+	if (trig[NL80211_WOWLAN_TRIG_ANY])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY;
+	if (trig[NL80211_WOWLAN_TRIG_DISCONNECT])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT;
+	if (trig[NL80211_WOWLAN_TRIG_MAGIC_PKT])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC;
+	if (trig[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE;
+	if (trig[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST;
+	if (trig[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE;
+	if (trig[NL80211_WOWLAN_TRIG_RFKILL_RELEASE])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE;
+	if (trig[NL80211_WOWLAN_TRIG_TCP_CONNECTION])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP;
+
+	return NL_SKIP;
+}
+
+static NMSettingWirelessWakeOnWLan
+wifi_nl80211_get_wake_on_wlan (NMWifiUtils *data)
+{
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMSettingWirelessWakeOnWLan wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+
+	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WOWLAN, 0);
+
+	nl80211_send_and_recv (nl80211, msg, nl80211_get_wake_on_wlan_handler, &wowl);
+
+	return wowl;
+}
+
 static gboolean
-wifi_nl80211_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl)
+wifi_nl80211_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct nlattr *triggers;
 	int err;
@@ -282,11 +340,11 @@ wifi_nl80211_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl)
 	if (wowl == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE)
 		return TRUE;
 
-	msg = nl80211_alloc_msg(nl80211, NL80211_CMD_SET_WOWLAN, 0);
+	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_WOWLAN, 0);
 	if (!msg)
 		return FALSE;
 
-	triggers = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS);
+	triggers = nla_nest_start (msg, NL80211_ATTR_WOWLAN_TRIGGERS);
 
 	if (NM_FLAGS_HAS (wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY))
 		NLA_PUT_FLAG (msg, NL80211_WOWLAN_TRIG_ANY);
@@ -306,7 +364,8 @@ wifi_nl80211_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl)
 	nla_nest_end(msg, triggers);
 
 	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
-	return err ? FALSE : TRUE;
+
+	return err >= 0;
 
 nla_put_failure:
 	return FALSE;
@@ -432,7 +491,7 @@ nl80211_bss_dump_handler (struct nl_msg *msg, void *arg)
 }
 
 static void
-nl80211_get_bss_info (WifiDataNl80211 *nl80211,
+nl80211_get_bss_info (NMWifiUtilsNl80211 *nl80211,
                       struct nl80211_bss_info *bss_info)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -445,9 +504,9 @@ nl80211_get_bss_info (WifiDataNl80211 *nl80211,
 }
 
 static guint32
-wifi_nl80211_get_freq (WifiData *data)
+wifi_nl80211_get_freq (NMWifiUtils *data)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_bss_info bss_info;
 
 	nl80211_get_bss_info (nl80211, &bss_info);
@@ -456,9 +515,9 @@ wifi_nl80211_get_freq (WifiData *data)
 }
 
 static guint32
-wifi_nl80211_find_freq (WifiData *data, const guint32 *freqs)
+wifi_nl80211_find_freq (NMWifiUtils *data, const guint32 *freqs)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	int i;
 
 	for (i = 0; i < nl80211->num_freqs; i++) {
@@ -472,9 +531,9 @@ wifi_nl80211_find_freq (WifiData *data, const guint32 *freqs)
 }
 
 static gboolean
-wifi_nl80211_get_bssid (WifiData *data, guint8 *out_bssid)
+wifi_nl80211_get_bssid (NMWifiUtils *data, guint8 *out_bssid)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_bss_info bss_info;
 
 	nl80211_get_bss_info (nl80211, &bss_info);
@@ -556,7 +615,7 @@ nl80211_station_handler (struct nl_msg *msg, void *arg)
 }
 
 static void
-nl80211_get_ap_info (WifiDataNl80211 *nl80211,
+nl80211_get_ap_info (NMWifiUtilsNl80211 *nl80211,
                      struct nl80211_station_info *sta_info)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -586,9 +645,9 @@ nla_put_failure:
 }
 
 static guint32
-wifi_nl80211_get_rate (WifiData *data)
+wifi_nl80211_get_rate (NMWifiUtils *data)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_station_info sta_info;
 
 	nl80211_get_ap_info (nl80211, &sta_info);
@@ -597,9 +656,9 @@ wifi_nl80211_get_rate (WifiData *data)
 }
 
 static int
-wifi_nl80211_get_qual (WifiData *data)
+wifi_nl80211_get_qual (NMWifiUtils *data)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_station_info sta_info;
 
 	nl80211_get_ap_info (nl80211, &sta_info);
@@ -607,9 +666,9 @@ wifi_nl80211_get_qual (WifiData *data)
 }
 
 static gboolean
-wifi_nl80211_indicate_addressing_running (WifiData *data, gboolean running)
+wifi_nl80211_indicate_addressing_running (NMWifiUtils *data, gboolean running)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
@@ -638,44 +697,6 @@ nla_put_failure:
 	return FALSE;
 }
 
-struct nl80211_wowlan_info {
-	gboolean enabled;
-};
-
-static int
-nl80211_wowlan_handler (struct nl_msg *msg, void *arg)
-{
-	struct nlattr *tb[NL80211_ATTR_MAX + 1];
-	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
-	struct nl80211_wowlan_info *info = arg;
-
-	info->enabled = FALSE;
-
-	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
-		return NL_SKIP;
-
-	if (tb[NL80211_ATTR_WOWLAN_TRIGGERS])
-		info->enabled = TRUE;
-
-	return NL_SKIP;
-}
-
-static gboolean
-wifi_nl80211_get_wowlan (WifiData *data)
-{
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
-	nm_auto_nlmsg struct nl_msg *msg = NULL;
-	struct nl80211_wowlan_info info;
-
-	if (!nl80211->can_wowlan)
-		return FALSE;
-
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WOWLAN, 0);
-	nl80211_send_and_recv (nl80211, msg, nl80211_wowlan_handler, &info);
-	return info.enabled;
-}
-
 struct nl80211_device_info {
 	int phy;
 	guint32 *freqs;
@@ -879,48 +900,58 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 	return NL_SKIP;
 }
 
-WifiData *
-wifi_nl80211_init (int ifindex)
+static void
+nm_wifi_utils_nl80211_init (NMWifiUtilsNl80211 *self)
 {
-	static const WifiDataClass klass = {
-		.struct_size = sizeof (WifiDataNl80211),
-		.get_mode = wifi_nl80211_get_mode,
-		.set_mode = wifi_nl80211_set_mode,
-		.set_powersave = wifi_nl80211_set_powersave,
-		.set_wake_on_wlan = wifi_nl80211_set_wake_on_wlan,
-		.get_freq = wifi_nl80211_get_freq,
-		.find_freq = wifi_nl80211_find_freq,
-		.get_bssid = wifi_nl80211_get_bssid,
-		.get_rate = wifi_nl80211_get_rate,
-		.get_qual = wifi_nl80211_get_qual,
-		.get_wowlan = wifi_nl80211_get_wowlan,
-		.indicate_addressing_running = wifi_nl80211_indicate_addressing_running,
-		.deinit = wifi_nl80211_deinit,
-	};
-	WifiDataNl80211 *nl80211;
+}
+
+static void
+nm_wifi_utils_nl80211_class_init (NMWifiUtilsNl80211Class *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMWifiUtilsClass *wifi_utils_class = NM_WIFI_UTILS_CLASS (klass);
+
+	object_class->dispose = dispose;
+
+	wifi_utils_class->get_mode = wifi_nl80211_get_mode;
+	wifi_utils_class->set_mode = wifi_nl80211_set_mode;
+	wifi_utils_class->set_powersave = wifi_nl80211_set_powersave;
+	wifi_utils_class->get_wake_on_wlan = wifi_nl80211_get_wake_on_wlan,
+	wifi_utils_class->set_wake_on_wlan = wifi_nl80211_set_wake_on_wlan,
+	wifi_utils_class->get_freq = wifi_nl80211_get_freq;
+	wifi_utils_class->find_freq = wifi_nl80211_find_freq;
+	wifi_utils_class->get_bssid = wifi_nl80211_get_bssid;
+	wifi_utils_class->get_rate = wifi_nl80211_get_rate;
+	wifi_utils_class->get_qual = wifi_nl80211_get_qual;
+	wifi_utils_class->indicate_addressing_running = wifi_nl80211_indicate_addressing_running;
+}
+
+NMWifiUtils *
+nm_wifi_utils_nl80211_new (int ifindex, struct nl_sock *genl)
+{
+	gs_unref_object NMWifiUtilsNl80211 *nl80211 = NULL;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct nl80211_device_info device_info = {};
 	char ifname[IFNAMSIZ];
 
+	if (!genl)
+		return NULL;
+
 	if (!nmp_utils_if_indextoname (ifindex, ifname)) {
 		_LOGW (LOGD_PLATFORM | LOGD_WIFI,
 		       "can't determine interface name for ifindex %d", ifindex);
 		nm_sprintf_buf (ifname, "if %d", ifindex);
 	}
 
-	nl80211 = wifi_data_new (&klass, ifindex);
-
-	nl80211->nl_sock = nl_socket_alloc ();
-	if (nl80211->nl_sock == NULL)
-		goto error;
+	nl80211 = g_object_new (NM_TYPE_WIFI_UTILS_NL80211, NULL);
 
-	if (nl_connect (nl80211->nl_sock, NETLINK_GENERIC))
-		goto error;
+	nl80211->parent.ifindex = ifindex;
+	nl80211->nl_sock = genl;
 
 	nl80211->id = genl_ctrl_resolve (nl80211->nl_sock, "nl80211");
 	if (nl80211->id < 0) {
 		_LOGD (LOGD_WIFI, "genl_ctrl_resolve: failed to resolve \"nl80211\"");
-		goto error;
+		return NULL;
 	}
 
 	nl80211->phy = -1;
@@ -932,42 +963,42 @@ wifi_nl80211_init (int ifindex)
 		_LOGD (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): NL80211_CMD_GET_WIPHY request failed",
 		       ifname);
-		goto error;
+		return NULL;
 	}
 
 	if (!device_info.success) {
 		_LOGD (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): NL80211_CMD_GET_WIPHY request indicated failure",
 		       ifname);
-		goto error;
+		return NULL;
 	}
 
 	if (!device_info.supported) {
 		_LOGD (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): driver does not fully support nl80211, falling back to WEXT",
 		       ifname);
-		goto error;
+		return NULL;
 	}
 
 	if (!device_info.can_scan_ssid) {
 		_LOGE (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): driver does not support SSID scans",
 		       ifname);
-		goto error;
+		return NULL;
 	}
 
 	if (device_info.num_freqs == 0 || device_info.freqs == NULL) {
 		nm_log_err (LOGD_PLATFORM | LOGD_WIFI,
 		            "(%s): driver reports no supported frequencies",
 		            ifname);
-		goto error;
+		return NULL;
 	}
 
 	if (device_info.caps == 0) {
 		_LOGE (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): driver doesn't report support of any encryption",
 		       ifname);
-		goto error;
+		return NULL;
 	}
 
 	nl80211->phy = device_info.phy;
@@ -979,10 +1010,5 @@ wifi_nl80211_init (int ifindex)
 	_LOGI (LOGD_PLATFORM | LOGD_WIFI,
 	       "(%s): using nl80211 for WiFi device control",
 	       ifname);
-	return (WifiData *) nl80211;
-
-error:
-	wifi_utils_unref ((WifiData *) nl80211);
-	return NULL;
+	return (NMWifiUtils *) g_steal_pointer (&nl80211);
 }
-
diff --git a/src/platform/wifi/wifi-utils-nl80211.h b/src/platform/wifi/nm-wifi-utils-nl80211.h
index aff24555..27f67697 100644
--- a/src/platform/wifi/wifi-utils-nl80211.h
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.h
@@ -16,13 +16,24 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2011 Intel Corporation. All rights reserved.
+ * Copyright (C) 2018 Red Hat, Inc.
  */
 
 #ifndef __WIFI_UTILS_NL80211_H__
 #define __WIFI_UTILS_NL80211_H__
 
-#include "wifi-utils.h"
+#include "nm-wifi-utils.h"
+#include "platform/nm-netlink.h"
 
-WifiData *wifi_nl80211_init (int ifindex);
+#define NM_TYPE_WIFI_UTILS_NL80211            (nm_wifi_utils_nl80211_get_type ())
+#define NM_WIFI_UTILS_NL80211(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211))
+#define NM_WIFI_UTILS_NL80211_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211Class))
+#define NM_IS_WIFI_UTILS_NL80211(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WIFI_UTILS_NL80211))
+#define NM_IS_WIFI_UTILS_NL80211_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WIFI_UTILS_NL80211))
+#define NM_WIFI_UTILS_NL80211_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211Class))
+
+GType nm_wifi_utils_nl80211_get_type (void);
+
+NMWifiUtils *nm_wifi_utils_nl80211_new (int ifindex, struct nl_sock *genl);
 
 #endif  /* __WIFI_UTILS_NL80211_H__ */
diff --git a/src/platform/wifi/wifi-utils-private.h b/src/platform/wifi/nm-wifi-utils-private.h
index 627108cb..bcbc1c51 100644
--- a/src/platform/wifi/wifi-utils-private.h
+++ b/src/platform/wifi/nm-wifi-utils-private.h
@@ -15,67 +15,65 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2011 Red Hat, Inc.
+ * Copyright (C) 2011 - 2018 Red Hat, Inc.
  */
 
 #ifndef __WIFI_UTILS_PRIVATE_H__
 #define __WIFI_UTILS_PRIVATE_H__
 
 #include "nm-dbus-interface.h"
-#include "wifi-utils.h"
+#include "nm-wifi-utils.h"
 
 typedef struct {
-	gsize struct_size;
+	GObjectClass parent;
 
-	NM80211Mode (*get_mode) (WifiData *data);
+	NM80211Mode (*get_mode) (NMWifiUtils *data);
 
-	gboolean (*set_mode) (WifiData *data, const NM80211Mode mode);
+	gboolean (*set_mode) (NMWifiUtils *data, const NM80211Mode mode);
 
 	/* Set power saving mode on an interface */
-	gboolean (*set_powersave) (WifiData *data, guint32 powersave);
+	gboolean (*set_powersave) (NMWifiUtils *data, guint32 powersave);
+
+	/* Get WakeOnWLAN configuration on an interface */
+	NMSettingWirelessWakeOnWLan (*get_wake_on_wlan) (NMWifiUtils *data);
 
 	/* Set WakeOnWLAN mode on an interface */
-	gboolean (*set_wake_on_wlan) (WifiData *data, NMSettingWirelessWakeOnWLan wowl);
+	gboolean (*set_wake_on_wlan) (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl);
 
 	/* Return current frequency in MHz (really associated BSS frequency) */
-	guint32 (*get_freq) (WifiData *data);
+	guint32 (*get_freq) (NMWifiUtils *data);
 
 	/* Return first supported frequency in the zero-terminated list */
-	guint32 (*find_freq) (WifiData *data, const guint32 *freqs);
+	guint32 (*find_freq) (NMWifiUtils *data, const guint32 *freqs);
 
 	/* Return current bitrate in Kbps */
-	guint32 (*get_rate) (WifiData *data);
+	guint32 (*get_rate) (NMWifiUtils *data);
 
-	gboolean (*get_bssid) (WifiData *data, guint8 *out_bssid);
+	gboolean (*get_bssid) (NMWifiUtils *data, guint8 *out_bssid);
 
 	/* Return a signal strength percentage 0 - 100% for the current BSSID;
 	 * return -1 on errors or if not associated.
 	 */
-	int (*get_qual) (WifiData *data);
-
-	void (*deinit) (WifiData *data);
-
-	gboolean (*get_wowlan) (WifiData *data);
+	int (*get_qual) (NMWifiUtils *data);
 
 	/* OLPC Mesh-only functions */
 
-	guint32 (*get_mesh_channel) (WifiData *data);
+	guint32 (*get_mesh_channel) (NMWifiUtils *data);
 
 	/* channel == 0 means "auto channel" */
-	gboolean (*set_mesh_channel) (WifiData *data, guint32 channel);
+	gboolean (*set_mesh_channel) (NMWifiUtils *data, guint32 channel);
 
 	/* ssid == NULL means "auto SSID" */
-	gboolean (*set_mesh_ssid) (WifiData *data, const guint8 *ssid, gsize len);
+	gboolean (*set_mesh_ssid) (NMWifiUtils *data, const guint8 *ssid, gsize len);
 
-	gboolean (*indicate_addressing_running) (WifiData *data, gboolean running);
-} WifiDataClass;
+	gboolean (*indicate_addressing_running) (NMWifiUtils *data, gboolean running);
+} NMWifiUtilsClass;
+
+struct NMWifiUtils {
+	GObject parent;
 
-struct WifiData {
-	const WifiDataClass *klass;
 	int ifindex;
 	NMDeviceWifiCapabilities caps;
 };
 
-gpointer wifi_data_new (const WifiDataClass *klass, int ifindex);
-
 #endif  /* __WIFI_UTILS_PRIVATE_H__ */
diff --git a/src/platform/wifi/wifi-utils-wext.c b/src/platform/wifi/nm-wifi-utils-wext.c
index c8744f79..e52ae5a3 100644
--- a/src/platform/wifi/wifi-utils-wext.c
+++ b/src/platform/wifi/nm-wifi-utils-wext.c
@@ -15,13 +15,13 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2005 - 2011 Red Hat, Inc.
+ * Copyright (C) 2005 - 2018 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
 #include "nm-default.h"
 
-#include "wifi-utils-wext.h"
+#include "nm-wifi-utils-wext.h"
 
 #include <errno.h>
 #include <string.h>
@@ -38,17 +38,24 @@
 #include <sys/socket.h>
 #include <linux/wireless.h>
 
-#include "wifi-utils-private.h"
+#include "nm-wifi-utils-private.h"
 #include "nm-utils.h"
 #include "platform/nm-platform-utils.h"
+#include "nm-core-internal.h"
 
 typedef struct {
-	WifiData parent;
+	NMWifiUtils parent;
 	int fd;
 	struct iw_quality max_qual;
 	gint8 num_freqs;
 	guint32 freqs[IW_MAX_FREQUENCIES];
-} WifiDataWext;
+} NMWifiUtilsWext;
+
+typedef struct {
+	NMWifiUtilsClass parent;
+} NMWifiUtilsWextClass;
+
+G_DEFINE_TYPE (NMWifiUtilsWext, nm_wifi_utils_wext, NM_TYPE_WIFI_UTILS)
 
 /* Until a new wireless-tools comes out that has the defs and the structure,
  * need to copy them here.
@@ -94,11 +101,11 @@ iw_freq_to_uint32 (const struct iw_freq *freq)
 }
 
 static void
-wifi_wext_deinit (WifiData *parent)
+dispose (GObject *object)
 {
-	WifiDataWext *wext = (WifiDataWext *) parent;
+	NMWifiUtilsWext *wext = NM_WIFI_UTILS_WEXT (object);
 
-	nm_close (wext->fd);
+	wext->fd = nm_close (wext->fd);
 }
 
 static gboolean
@@ -118,9 +125,9 @@ get_ifname (int ifindex, char *buffer, const char *op)
 }
 
 static NM80211Mode
-wifi_wext_get_mode_ifname (WifiData *data, const char *ifname)
+wifi_wext_get_mode_ifname (NMWifiUtils *data, const char *ifname)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 
 	memset (&wrq, 0, sizeof (struct iwreq));
@@ -150,7 +157,7 @@ wifi_wext_get_mode_ifname (WifiData *data, const char *ifname)
 }
 
 static NM80211Mode
-wifi_wext_get_mode (WifiData *data)
+wifi_wext_get_mode (NMWifiUtils *data)
 {
 	char ifname[IFNAMSIZ];
 
@@ -161,9 +168,9 @@ wifi_wext_get_mode (WifiData *data)
 }
 
 static gboolean
-wifi_wext_set_mode (WifiData *data, const NM80211Mode mode)
+wifi_wext_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	char ifname[IFNAMSIZ];
 
@@ -203,9 +210,9 @@ wifi_wext_set_mode (WifiData *data, const NM80211Mode mode)
 }
 
 static gboolean
-wifi_wext_set_powersave (WifiData *data, guint32 powersave)
+wifi_wext_set_powersave (NMWifiUtils *data, guint32 powersave)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	char ifname[IFNAMSIZ];
 
@@ -232,9 +239,9 @@ wifi_wext_set_powersave (WifiData *data, guint32 powersave)
 }
 
 static guint32
-wifi_wext_get_freq (WifiData *data)
+wifi_wext_get_freq (NMWifiUtils *data)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	char ifname[IFNAMSIZ];
 
@@ -254,9 +261,9 @@ wifi_wext_get_freq (WifiData *data)
 }
 
 static guint32
-wifi_wext_find_freq (WifiData *data, const guint32 *freqs)
+wifi_wext_find_freq (NMWifiUtils *data, const guint32 *freqs)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	int i;
 
 	for (i = 0; i < wext->num_freqs; i++) {
@@ -270,9 +277,9 @@ wifi_wext_find_freq (WifiData *data, const guint32 *freqs)
 }
 
 static gboolean
-wifi_wext_get_bssid (WifiData *data, guint8 *out_bssid)
+wifi_wext_get_bssid (NMWifiUtils *data, guint8 *out_bssid)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	char ifname[IFNAMSIZ];
 
@@ -292,9 +299,9 @@ wifi_wext_get_bssid (WifiData *data, guint8 *out_bssid)
 }
 
 static guint32
-wifi_wext_get_rate (WifiData *data)
+wifi_wext_get_rate (NMWifiUtils *data)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	int err;
 	char ifname[IFNAMSIZ];
@@ -403,9 +410,9 @@ wext_qual_to_percent (const struct iw_quality *qual,
 }
 
 static int
-wifi_wext_get_qual (WifiData *data)
+wifi_wext_get_qual (NMWifiUtils *data)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	struct iw_statistics stats;
 	char ifname[IFNAMSIZ];
@@ -433,13 +440,13 @@ wifi_wext_get_qual (WifiData *data)
 /* OLPC Mesh-only functions */
 
 static guint32
-wifi_wext_get_mesh_channel (WifiData *data)
+wifi_wext_get_mesh_channel (NMWifiUtils *data)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	guint32 freq;
 	int i;
 
-	freq = wifi_utils_get_freq (data);
+	freq = nm_wifi_utils_get_freq (data);
 	for (i = 0; i < wext->num_freqs; i++) {
 		if (freq == wext->freqs[i])
 			return i + 1;
@@ -448,9 +455,9 @@ wifi_wext_get_mesh_channel (WifiData *data)
 }
 
 static gboolean
-wifi_wext_set_mesh_channel (WifiData *data, guint32 channel)
+wifi_wext_set_mesh_channel (NMWifiUtils *data, guint32 channel)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	char ifname[IFNAMSIZ];
 
@@ -477,9 +484,9 @@ wifi_wext_set_mesh_channel (WifiData *data, guint32 channel)
 }
 
 static gboolean
-wifi_wext_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len)
+wifi_wext_set_mesh_ssid (NMWifiUtils *data, const guint8 *ssid, gsize len)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	char buf[IW_ESSID_MAX_SIZE + 1];
 	char ifname[IFNAMSIZ];
@@ -500,11 +507,13 @@ wifi_wext_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len)
 		return TRUE;
 
 	if (errno != ENODEV) {
+		gs_free char *ssid_str = NULL;
+
 		errsv = errno;
 		_LOGE (LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC,
 		       "(%s): error setting SSID to '%s': %s",
 		       ifname,
-		       ssid ? nm_utils_escape_ssid (ssid, len) : "(null)",
+		       (ssid_str = _nm_utils_ssid_to_string_arr (ssid, len)),
 		       strerror (errsv));
 	}
 
@@ -514,7 +523,7 @@ wifi_wext_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len)
 /*****************************************************************************/
 
 static gboolean
-wext_can_scan_ifname (WifiDataWext *wext, const char *ifname)
+wext_can_scan_ifname (NMWifiUtilsWext *wext, const char *ifname)
 {
 	struct iwreq wrq;
 
@@ -528,7 +537,7 @@ wext_can_scan_ifname (WifiDataWext *wext, const char *ifname)
 }
 
 static gboolean
-wext_get_range_ifname (WifiDataWext *wext,
+wext_get_range_ifname (NMWifiUtilsWext *wext,
                        const char *ifname,
                        struct iw_range *range,
                        guint32 *response_len)
@@ -577,7 +586,7 @@ wext_get_range_ifname (WifiDataWext *wext,
                   NM_WIFI_DEVICE_CAP_RSN)
 
 static guint32
-wext_get_caps (WifiDataWext *wext, const char *ifname, struct iw_range *range)
+wext_get_caps (NMWifiUtilsWext *wext, const char *ifname, struct iw_range *range)
 {
 	guint32 caps = NM_WIFI_DEVICE_CAP_NONE;
 
@@ -626,25 +635,38 @@ wext_get_caps (WifiDataWext *wext, const char *ifname, struct iw_range *range)
 	return caps;
 }
 
-WifiData *
-wifi_wext_init (int ifindex, gboolean check_scan)
+/*****************************************************************************/
+
+static void
+nm_wifi_utils_wext_init (NMWifiUtilsWext *self)
+{
+}
+
+static void
+nm_wifi_utils_wext_class_init (NMWifiUtilsWextClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMWifiUtilsClass *wifi_utils_class = NM_WIFI_UTILS_CLASS (klass);
+
+	object_class->dispose = dispose;
+
+	wifi_utils_class->get_mode = wifi_wext_get_mode;
+	wifi_utils_class->set_mode = wifi_wext_set_mode;
+	wifi_utils_class->set_powersave = wifi_wext_set_powersave;
+	wifi_utils_class->get_freq = wifi_wext_get_freq;
+	wifi_utils_class->find_freq = wifi_wext_find_freq;
+	wifi_utils_class->get_bssid = wifi_wext_get_bssid;
+	wifi_utils_class->get_rate = wifi_wext_get_rate;
+	wifi_utils_class->get_qual = wifi_wext_get_qual;
+	wifi_utils_class->get_mesh_channel = wifi_wext_get_mesh_channel;
+	wifi_utils_class->set_mesh_channel = wifi_wext_set_mesh_channel;
+	wifi_utils_class->set_mesh_ssid = wifi_wext_set_mesh_ssid;
+}
+
+NMWifiUtils *
+nm_wifi_utils_wext_new (int ifindex, gboolean check_scan)
 {
-	static const WifiDataClass klass = {
-		.struct_size = sizeof (WifiDataWext),
-		.get_mode = wifi_wext_get_mode,
-		.set_mode = wifi_wext_set_mode,
-		.set_powersave = wifi_wext_set_powersave,
-		.get_freq = wifi_wext_get_freq,
-		.find_freq = wifi_wext_find_freq,
-		.get_bssid = wifi_wext_get_bssid,
-		.get_rate = wifi_wext_get_rate,
-		.get_qual = wifi_wext_get_qual,
-		.deinit = wifi_wext_deinit,
-		.get_mesh_channel = wifi_wext_get_mesh_channel,
-		.set_mesh_channel = wifi_wext_set_mesh_channel,
-		.set_mesh_ssid = wifi_wext_set_mesh_ssid,
-	};
-	WifiDataWext *wext;
+	NMWifiUtilsWext *wext;
 	struct iw_range range;
 	guint32 response_len = 0;
 	struct iw_range_with_scan_capa *scan_capa_range;
@@ -658,8 +680,9 @@ wifi_wext_init (int ifindex, gboolean check_scan)
 		return NULL;
 	}
 
-	wext = wifi_data_new (&klass, ifindex);
+	wext = g_object_new (NM_TYPE_WIFI_UTILS_WEXT, NULL);
 
+	wext->parent.ifindex = ifindex;
 	wext->fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
 	if (wext->fd < 0)
 		goto error;
@@ -731,15 +754,15 @@ wifi_wext_init (int ifindex, gboolean check_scan)
 	       "(%s): using WEXT for WiFi device control",
 	       ifname);
 
-	return (WifiData *) wext;
+	return (NMWifiUtils *) wext;
 
 error:
-	wifi_utils_unref ((WifiData *) wext);
+	g_object_unref (wext);
 	return NULL;
 }
 
 gboolean
-wifi_wext_is_wifi (const char *iface)
+nm_wifi_utils_wext_is_wifi (const char *iface)
 {
 	int fd;
 	struct iwreq iwr;
diff --git a/src/platform/wifi/wifi-utils-wext.h b/src/platform/wifi/nm-wifi-utils-wext.h
index 3ef5a173..44b11afb 100644
--- a/src/platform/wifi/wifi-utils-wext.h
+++ b/src/platform/wifi/nm-wifi-utils-wext.h
@@ -15,16 +15,25 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2011 Red Hat, Inc.
+ * Copyright (C) 2011 - 2018 Red Hat, Inc.
  */
 
 #ifndef __WIFI_UTILS_WEXT_H__
 #define __WIFI_UTILS_WEXT_H__
 
-#include "wifi-utils.h"
+#include "nm-wifi-utils.h"
 
-WifiData *wifi_wext_init (int ifindex, gboolean check_scan);
+#define NM_TYPE_WIFI_UTILS_WEXT            (nm_wifi_utils_wext_get_type ())
+#define NM_WIFI_UTILS_WEXT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWext))
+#define NM_WIFI_UTILS_WEXT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWextClass))
+#define NM_IS_WIFI_UTILS_WEXT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WIFI_UTILS_WEXT))
+#define NM_IS_WIFI_UTILS_WEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WIFI_UTILS_WEXT))
+#define NM_WIFI_UTILS_WEXT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWextClass))
 
-gboolean wifi_wext_is_wifi (const char *iface);
+GType nm_wifi_utils_wext_get_type (void);
+
+NMWifiUtils *nm_wifi_utils_wext_new (int ifindex, gboolean check_scan);
+
+gboolean nm_wifi_utils_wext_is_wifi (const char *iface);
 
 #endif  /* __WIFI_UTILS_WEXT_H__ */
diff --git a/src/platform/wifi/nm-wifi-utils.c b/src/platform/wifi/nm-wifi-utils.c
new file mode 100644
index 00000000..25d71c6a
--- /dev/null
+++ b/src/platform/wifi/nm-wifi-utils.c
@@ -0,0 +1,240 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2005 - 2018 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-wifi-utils.h"
+
+#include <sys/stat.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "nm-wifi-utils-private.h"
+#include "nm-wifi-utils-nl80211.h"
+#if HAVE_WEXT
+#include "nm-wifi-utils-wext.h"
+#endif
+#include "nm-core-utils.h"
+
+#include "platform/nm-platform-utils.h"
+
+G_DEFINE_ABSTRACT_TYPE (NMWifiUtils, nm_wifi_utils, G_TYPE_OBJECT)
+
+/*****************************************************************************/
+
+static void
+nm_wifi_utils_init (NMWifiUtils *self)
+{
+}
+
+static void
+nm_wifi_utils_class_init (NMWifiUtilsClass *klass)
+{
+}
+
+NMWifiUtils *
+nm_wifi_utils_new (int ifindex, struct nl_sock *genl, gboolean check_scan)
+{
+	NMWifiUtils *ret;
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+
+	ret = nm_wifi_utils_nl80211_new (ifindex, genl);
+
+#if HAVE_WEXT
+	if (ret == NULL)
+		ret = nm_wifi_utils_wext_new (ifindex, check_scan);
+#endif
+
+	return ret;
+}
+
+NMDeviceWifiCapabilities
+nm_wifi_utils_get_caps (NMWifiUtils *data)
+{
+	g_return_val_if_fail (data != NULL, NM_WIFI_DEVICE_CAP_NONE);
+
+	return data->caps;
+}
+
+NM80211Mode
+nm_wifi_utils_get_mode (NMWifiUtils *data)
+{
+	g_return_val_if_fail (data != NULL, NM_802_11_MODE_UNKNOWN);
+	return NM_WIFI_UTILS_GET_CLASS (data)->get_mode (data);
+}
+
+gboolean
+nm_wifi_utils_set_mode (NMWifiUtils *data, const NM80211Mode mode)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+	g_return_val_if_fail (   (mode == NM_802_11_MODE_INFRA)
+	                      || (mode == NM_802_11_MODE_AP)
+	                      || (mode == NM_802_11_MODE_ADHOC), FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+
+	/* nl80211 probably doesn't need this */
+	return klass->set_mode ? klass->set_mode (data, mode) : TRUE;
+}
+
+gboolean
+nm_wifi_utils_set_powersave (NMWifiUtils *data, guint32 powersave)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+	return klass->set_powersave ? klass->set_powersave (data, powersave) : TRUE;
+}
+
+NMSettingWirelessWakeOnWLan
+nm_wifi_utils_get_wake_on_wlan (NMWifiUtils *data)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+
+	return klass->get_wake_on_wlan ? klass->get_wake_on_wlan (data) : NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
+}
+
+gboolean
+nm_wifi_utils_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+	return klass->set_wake_on_wlan ? klass->set_wake_on_wlan (data, wowl) : FALSE;
+}
+
+guint32
+nm_wifi_utils_get_freq (NMWifiUtils *data)
+{
+	g_return_val_if_fail (data != NULL, 0);
+	return NM_WIFI_UTILS_GET_CLASS (data)->get_freq (data);
+}
+
+guint32
+nm_wifi_utils_find_freq (NMWifiUtils *data, const guint32 *freqs)
+{
+	g_return_val_if_fail (data != NULL, 0);
+	g_return_val_if_fail (freqs != NULL, 0);
+	return NM_WIFI_UTILS_GET_CLASS (data)->find_freq (data, freqs);
+}
+
+gboolean
+nm_wifi_utils_get_bssid (NMWifiUtils *data, guint8 *out_bssid)
+{
+	g_return_val_if_fail (data != NULL, FALSE);
+	g_return_val_if_fail (out_bssid != NULL, FALSE);
+
+	memset (out_bssid, 0, ETH_ALEN);
+	return NM_WIFI_UTILS_GET_CLASS (data)->get_bssid (data, out_bssid);
+}
+
+guint32
+nm_wifi_utils_get_rate (NMWifiUtils *data)
+{
+	g_return_val_if_fail (data != NULL, 0);
+	return NM_WIFI_UTILS_GET_CLASS (data)->get_rate (data);
+}
+
+int
+nm_wifi_utils_get_qual (NMWifiUtils *data)
+{
+	g_return_val_if_fail (data != NULL, 0);
+	return NM_WIFI_UTILS_GET_CLASS (data)->get_qual (data);
+}
+
+gboolean
+nm_wifi_utils_is_wifi (int dirfd, const char *ifname)
+{
+	g_return_val_if_fail (dirfd >= 0, FALSE);
+
+	if (faccessat (dirfd, "phy80211", F_OK, 0) == 0)
+		return TRUE;
+#if HAVE_WEXT
+	if (nm_wifi_utils_wext_is_wifi (ifname))
+		return TRUE;
+#endif
+	return FALSE;
+}
+
+/* OLPC Mesh-only functions */
+
+guint32
+nm_wifi_utils_get_mesh_channel (NMWifiUtils *data)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+	g_return_val_if_fail (klass->get_mesh_channel != NULL, FALSE);
+
+	return klass->get_mesh_channel (data);
+}
+
+gboolean
+nm_wifi_utils_set_mesh_channel (NMWifiUtils *data, guint32 channel)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+	g_return_val_if_fail (channel <= 13, FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+	g_return_val_if_fail (klass->set_mesh_channel != NULL, FALSE);
+
+	return klass->set_mesh_channel (data, channel);
+}
+
+gboolean
+nm_wifi_utils_set_mesh_ssid (NMWifiUtils *data, const guint8 *ssid, gsize len)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+	g_return_val_if_fail (klass->set_mesh_ssid != NULL, FALSE);
+
+	return klass->set_mesh_ssid (data, ssid, len);
+}
+
+gboolean
+nm_wifi_utils_indicate_addressing_running (NMWifiUtils *data, gboolean running)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+	return klass->indicate_addressing_running ? klass->indicate_addressing_running (data, running) : FALSE;
+}
diff --git a/src/platform/wifi/nm-wifi-utils.h b/src/platform/wifi/nm-wifi-utils.h
new file mode 100644
index 00000000..6cd178bd
--- /dev/null
+++ b/src/platform/wifi/nm-wifi-utils.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2005 - 2018 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#ifndef __WIFI_UTILS_H__
+#define __WIFI_UTILS_H__
+
+#include <net/ethernet.h>
+
+#include "nm-dbus-interface.h"
+#include "nm-setting-wireless.h"
+#include "platform/nm-netlink.h"
+
+typedef struct NMWifiUtils NMWifiUtils;
+
+#define NM_TYPE_WIFI_UTILS            (nm_wifi_utils_get_type ())
+#define NM_WIFI_UTILS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIFI_UTILS, NMWifiUtils))
+#define NM_WIFI_UTILS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WIFI_UTILS, NMWifiUtilsClass))
+#define NM_IS_WIFI_UTILS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WIFI_UTILS))
+#define NM_IS_WIFI_UTILS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WIFI_UTILS))
+#define NM_WIFI_UTILS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WIFI_UTILS, NMWifiUtilsClass))
+
+GType nm_wifi_utils_get_type (void);
+
+gboolean nm_wifi_utils_is_wifi (int dirfd, const char *ifname);
+
+NMWifiUtils *nm_wifi_utils_new (int ifindex, struct nl_sock *genl, gboolean check_scan);
+
+NMDeviceWifiCapabilities nm_wifi_utils_get_caps (NMWifiUtils *data);
+
+NM80211Mode nm_wifi_utils_get_mode (NMWifiUtils *data);
+
+gboolean nm_wifi_utils_set_mode (NMWifiUtils *data, const NM80211Mode mode);
+
+/* Returns frequency in MHz */
+guint32 nm_wifi_utils_get_freq (NMWifiUtils *data);
+
+/* Return the first supported frequency in the zero-terminated list.
+ * Frequencies are specified in MHz. */
+guint32 nm_wifi_utils_find_freq (NMWifiUtils *data, const guint32 *freqs);
+
+/* out_bssid must be ETH_ALEN bytes */
+gboolean nm_wifi_utils_get_bssid (NMWifiUtils *data, guint8 *out_bssid);
+
+/* Returns current bitrate in Kbps */
+guint32 nm_wifi_utils_get_rate (NMWifiUtils *data);
+
+/* Returns quality 0 - 100% on succes, or -1 on error */
+int nm_wifi_utils_get_qual (NMWifiUtils *data);
+
+/* Tells the driver DHCP or SLAAC is running */
+gboolean nm_wifi_utils_indicate_addressing_running (NMWifiUtils *data, gboolean running);
+
+gboolean nm_wifi_utils_set_powersave (NMWifiUtils *data, guint32 powersave);
+
+NMSettingWirelessWakeOnWLan nm_wifi_utils_get_wake_on_wlan (NMWifiUtils *data);
+
+gboolean nm_wifi_utils_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl);
+
+/* OLPC Mesh-only functions */
+guint32 nm_wifi_utils_get_mesh_channel (NMWifiUtils *data);
+
+gboolean nm_wifi_utils_set_mesh_channel (NMWifiUtils *data, guint32 channel);
+
+gboolean nm_wifi_utils_set_mesh_ssid (NMWifiUtils *data, const guint8 *ssid, gsize len);
+
+#endif  /* __WIFI_UTILS_H__ */
diff --git a/src/platform/wifi/wifi-utils.c b/src/platform/wifi/wifi-utils.c
deleted file mode 100644
index b3dd92ba..00000000
--- a/src/platform/wifi/wifi-utils.c
+++ /dev/null
@@ -1,231 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2005 - 2011 Red Hat, Inc.
- * Copyright (C) 2006 - 2008 Novell, Inc.
- */
-
-#include "nm-default.h"
-
-#include "wifi-utils.h"
-
-#include <sys/stat.h>
-#include <stdio.h>
-#include <string.h>
-#include <fcntl.h>
-
-#include "wifi-utils-private.h"
-#include "wifi-utils-nl80211.h"
-#if HAVE_WEXT
-#include "wifi-utils-wext.h"
-#endif
-#include "nm-core-utils.h"
-
-#include "platform/nm-platform-utils.h"
-
-gpointer
-wifi_data_new (const WifiDataClass *klass, int ifindex)
-{
-	WifiData *data;
-
-	nm_assert (klass);
-	nm_assert (klass->struct_size > sizeof (WifiData));
-
-	data = g_malloc0 (klass->struct_size);
-	data->klass = klass;
-	data->ifindex = ifindex;
-	return data;
-}
-
-/*****************************************************************************/
-
-WifiData *
-wifi_utils_init (int ifindex, gboolean check_scan)
-{
-	WifiData *ret;
-
-	g_return_val_if_fail (ifindex > 0, NULL);
-
-	ret = wifi_nl80211_init (ifindex);
-	if (ret == NULL) {
-#if HAVE_WEXT
-		ret = wifi_wext_init (ifindex, check_scan);
-#endif
-	}
-	return ret;
-}
-
-int
-wifi_utils_get_ifindex (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, -1);
-
-	return data->ifindex;
-}
-
-NMDeviceWifiCapabilities
-wifi_utils_get_caps (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, NM_WIFI_DEVICE_CAP_NONE);
-
-	return data->caps;
-}
-
-NM80211Mode
-wifi_utils_get_mode (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, NM_802_11_MODE_UNKNOWN);
-	return data->klass->get_mode (data);
-}
-
-gboolean
-wifi_utils_set_mode (WifiData *data, const NM80211Mode mode)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (   (mode == NM_802_11_MODE_INFRA)
-	                      || (mode == NM_802_11_MODE_AP)
-	                      || (mode == NM_802_11_MODE_ADHOC), FALSE);
-
-	/* nl80211 probably doesn't need this */
-	return data->klass->set_mode ? data->klass->set_mode (data, mode) : TRUE;
-}
-
-gboolean
-wifi_utils_set_powersave (WifiData *data, guint32 powersave)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-
-	return data->klass->set_powersave ? data->klass->set_powersave (data, powersave) : TRUE;
-}
-
-gboolean
-wifi_utils_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-
-	return data->klass->set_wake_on_wlan ?
-	       data->klass->set_wake_on_wlan (data, wowl) : FALSE;
-}
-
-guint32
-wifi_utils_get_freq (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, 0);
-	return data->klass->get_freq (data);
-}
-
-guint32
-wifi_utils_find_freq (WifiData *data, const guint32 *freqs)
-{
-	g_return_val_if_fail (data != NULL, 0);
-	g_return_val_if_fail (freqs != NULL, 0);
-	return data->klass->find_freq (data, freqs);
-}
-
-gboolean
-wifi_utils_get_bssid (WifiData *data, guint8 *out_bssid)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (out_bssid != NULL, FALSE);
-
-	memset (out_bssid, 0, ETH_ALEN);
-	return data->klass->get_bssid (data, out_bssid);
-}
-
-guint32
-wifi_utils_get_rate (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, 0);
-	return data->klass->get_rate (data);
-}
-
-int
-wifi_utils_get_qual (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, 0);
-	return data->klass->get_qual (data);
-}
-
-gboolean
-wifi_utils_get_wowlan (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, 0);
-
-	if (!data->klass->get_wowlan)
-		return FALSE;
-	return data->klass->get_wowlan (data);
-}
-
-void
-wifi_utils_unref (WifiData *data)
-{
-	g_return_if_fail (data != NULL);
-
-	data->klass->deinit (data);
-	g_free (data);
-}
-
-gboolean
-wifi_utils_is_wifi (int dirfd, const char *ifname)
-{
-	g_return_val_if_fail (dirfd >= 0, FALSE);
-
-	if (faccessat (dirfd, "phy80211", F_OK, 0) == 0)
-		return TRUE;
-#if HAVE_WEXT
-	if (wifi_wext_is_wifi (ifname))
-		return TRUE;
-#endif
-	return FALSE;
-}
-
-/* OLPC Mesh-only functions */
-
-guint32
-wifi_utils_get_mesh_channel (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (data->klass->get_mesh_channel != NULL, FALSE);
-	return data->klass->get_mesh_channel (data);
-}
-
-gboolean
-wifi_utils_set_mesh_channel (WifiData *data, guint32 channel)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (channel <= 13, FALSE);
-	g_return_val_if_fail (data->klass->set_mesh_channel != NULL, FALSE);
-	return data->klass->set_mesh_channel (data, channel);
-}
-
-gboolean
-wifi_utils_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (data->klass->set_mesh_ssid != NULL, FALSE);
-	return data->klass->set_mesh_ssid (data, ssid, len);
-}
-
-gboolean
-wifi_utils_indicate_addressing_running (WifiData *data, gboolean running)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-	if (data->klass->indicate_addressing_running)
-		return data->klass->indicate_addressing_running (data, running);
-	return FALSE;
-}
-
diff --git a/src/platform/wifi/wifi-utils.h b/src/platform/wifi/wifi-utils.h
deleted file mode 100644
index c69561de..00000000
--- a/src/platform/wifi/wifi-utils.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2005 - 2011 Red Hat, Inc.
- * Copyright (C) 2006 - 2008 Novell, Inc.
- */
-
-#ifndef __WIFI_UTILS_H__
-#define __WIFI_UTILS_H__
-
-#include <net/ethernet.h>
-
-#include "nm-dbus-interface.h"
-#include "nm-setting-wireless.h"
-
-typedef struct WifiData WifiData;
-
-gboolean wifi_utils_is_wifi (int dirfd, const char *ifname);
-
-WifiData *wifi_utils_init (int ifindex, gboolean check_scan);
-
-int wifi_utils_get_ifindex (WifiData *data);
-
-void wifi_utils_unref (WifiData *data);
-
-NMDeviceWifiCapabilities wifi_utils_get_caps (WifiData *data);
-
-NM80211Mode wifi_utils_get_mode (WifiData *data);
-
-gboolean wifi_utils_set_mode (WifiData *data, const NM80211Mode mode);
-
-/* Returns frequency in MHz */
-guint32 wifi_utils_get_freq (WifiData *data);
-
-/* Return the first supported frequency in the zero-terminated list.
- * Frequencies are specified in MHz. */
-guint32 wifi_utils_find_freq (WifiData *data, const guint32 *freqs);
-
-/* out_bssid must be ETH_ALEN bytes */
-gboolean wifi_utils_get_bssid (WifiData *data, guint8 *out_bssid);
-
-/* Returns current bitrate in Kbps */
-guint32 wifi_utils_get_rate (WifiData *data);
-
-/* Returns quality 0 - 100% on succes, or -1 on error */
-int wifi_utils_get_qual (WifiData *data);
-
-/* Tells the driver DHCP or SLAAC is running */
-gboolean wifi_utils_indicate_addressing_running (WifiData *data, gboolean running);
-
-/* Returns true if WoWLAN is enabled on device */
-gboolean wifi_utils_get_wowlan (WifiData *data);
-
-gboolean wifi_utils_set_powersave (WifiData *data, guint32 powersave);
-
-gboolean wifi_utils_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl);
-
-/* OLPC Mesh-only functions */
-guint32 wifi_utils_get_mesh_channel (WifiData *data);
-
-gboolean wifi_utils_set_mesh_channel (WifiData *data, guint32 channel);
-
-gboolean wifi_utils_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len);
-
-#endif  /* __WIFI_UTILS_H__ */
diff --git a/src/platform/wpan/nm-wpan-utils.c b/src/platform/wpan/nm-wpan-utils.c
new file mode 100644
index 00000000..0544539a
--- /dev/null
+++ b/src/platform/wpan/nm-wpan-utils.c
@@ -0,0 +1,286 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-wpan-utils.h"
+
+#include "platform/linux/nl802154.h"
+#include "platform/nm-netlink.h"
+
+#define _NMLOG_PREFIX_NAME "wpan-nl802154"
+#define _NMLOG(level, domain, ...) \
+	G_STMT_START { \
+		nm_log ((level), (domain), NULL, NULL, \
+		        "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+		        _NMLOG_PREFIX_NAME \
+		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+	} G_STMT_END
+
+/*****************************************************************************/
+
+struct NMWpanUtils {
+        GObject parent;
+	int ifindex;
+	struct nl_sock *nl_sock;
+	int id;
+};
+
+typedef struct {
+        GObjectClass parent;
+} NMWpanUtilsClass;
+
+G_DEFINE_TYPE (NMWpanUtils, nm_wpan_utils, G_TYPE_OBJECT)
+
+/*****************************************************************************/
+
+static int
+ack_handler (struct nl_msg *msg, void *arg)
+{
+	int *done = arg;
+	*done = 1;
+	return NL_STOP;
+}
+
+static int
+finish_handler (struct nl_msg *msg, void *arg)
+{
+	int *done = arg;
+	*done = 1;
+	return NL_SKIP;
+}
+
+static int
+error_handler (struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg)
+{
+	int *done = arg;
+	*done = err->error;
+	return NL_SKIP;
+}
+
+static struct nl_msg *
+_nl802154_alloc_msg (int id, int ifindex, guint32 cmd, guint32 flags)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+
+	msg = nlmsg_alloc ();
+	genlmsg_put (msg, 0, 0, id, 0, flags, cmd, 0);
+	NLA_PUT_U32 (msg, NL802154_ATTR_IFINDEX, ifindex);
+	return g_steal_pointer (&msg);
+
+nla_put_failure:
+	return NULL;
+}
+
+static struct nl_msg *
+nl802154_alloc_msg (NMWpanUtils *self, guint32 cmd, guint32 flags)
+{
+	return _nl802154_alloc_msg (self->id, self->ifindex, cmd, flags);
+}
+
+static int
+_nl802154_send_and_recv (struct nl_sock *nl_sock,
+                         struct nl_msg *msg,
+                         int (*valid_handler) (struct nl_msg *, void *),
+                         void *valid_data)
+{
+	int err;
+	int done = 0;
+	const struct nl_cb cb = {
+		.err_cb     = error_handler,
+		.err_arg    = &done,
+		.finish_cb  = finish_handler,
+		.finish_arg = &done,
+		.ack_cb     = ack_handler,
+		.ack_arg    = &done,
+		.valid_cb   = valid_handler,
+		.valid_arg  = valid_data,
+	};
+
+	g_return_val_if_fail (msg != NULL, -ENOMEM);
+
+	err = nl_send_auto (nl_sock, msg);
+	if (err < 0)
+		return err;
+
+	/* Loop until one of our NL callbacks says we're done; on success
+	 * done will be 1, on error it will be < 0.
+	 */
+	while (!done) {
+		err = nl_recvmsgs (nl_sock, &cb);
+		if (err < 0 && err != -EAGAIN) {
+			_LOGW (LOGD_PLATFORM, "nl_recvmsgs() error: (%d) %s",
+			       err, nl_geterror (err));
+			break;
+		}
+	}
+
+	if (err >= 0 && done < 0)
+		err = done;
+	return err;
+}
+
+static int
+nl802154_send_and_recv (NMWpanUtils *self,
+                        struct nl_msg *msg,
+                        int (*valid_handler) (struct nl_msg *, void *),
+                        void *valid_data)
+{
+	return _nl802154_send_and_recv (self->nl_sock, msg,
+	                                valid_handler, valid_data);
+}
+
+struct nl802154_interface {
+	guint16 pan_id;
+	guint16 short_addr;
+
+	gboolean valid;
+};
+
+static int
+nl802154_get_interface_handler (struct nl_msg *msg, void *arg)
+{
+	struct nl802154_interface *info = arg;
+	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
+	struct nlattr *tb[NL802154_ATTR_MAX + 1] = { 0, };
+	static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX + 1] = {
+		[NL802154_ATTR_PAN_ID] =            { .type = NLA_U16 },
+		[NL802154_ATTR_SHORT_ADDR] =        { .type = NLA_U16 },
+	};
+
+	if (nla_parse (tb, NL802154_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
+	               genlmsg_attrlen (gnlh, 0), nl802154_policy) < 0)
+	return NL_SKIP;
+
+	if (tb[NL802154_ATTR_PAN_ID])
+		info->pan_id = le16toh (nla_get_u16 (tb[NL802154_ATTR_PAN_ID]));
+
+	if (tb[NL802154_ATTR_SHORT_ADDR])
+		info->short_addr = le16toh (nla_get_u16 (tb[NL802154_ATTR_SHORT_ADDR]));
+
+	info->valid = TRUE;
+
+	return NL_SKIP;
+}
+
+static void
+nl802154_get_interface (NMWpanUtils *self,
+                        struct nl802154_interface *interface)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+
+	memset (interface, 0, sizeof (*interface));
+
+	msg = nl802154_alloc_msg (self, NL802154_CMD_GET_INTERFACE, 0);
+
+	nl802154_send_and_recv (self, msg, nl802154_get_interface_handler, interface);
+}
+
+/*****************************************************************************/
+
+guint16
+nm_wpan_utils_get_pan_id (NMWpanUtils *self)
+{
+	struct nl802154_interface interface;
+
+	nl802154_get_interface (self, &interface);
+
+	return interface.pan_id;
+}
+
+gboolean
+nm_wpan_utils_set_pan_id (NMWpanUtils *self, guint16 pan_id)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	int err;
+
+	g_return_val_if_fail (self != NULL, FALSE);
+
+	msg = nl802154_alloc_msg (self, NL802154_CMD_SET_PAN_ID, 0);
+	NLA_PUT_U16 (msg, NL802154_ATTR_PAN_ID, htole16 (pan_id));
+	err = nl802154_send_and_recv (self, msg, NULL, NULL);
+	return err >= 0;
+
+nla_put_failure:
+	return FALSE;
+}
+
+guint16
+nm_wpan_utils_get_short_addr (NMWpanUtils *self)
+{
+	struct nl802154_interface interface;
+
+	nl802154_get_interface (self, &interface);
+
+	return interface.short_addr;
+}
+
+gboolean
+nm_wpan_utils_set_short_addr (NMWpanUtils *self, guint16 short_addr)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	int err;
+
+	g_return_val_if_fail (self != NULL, FALSE);
+
+	msg = nl802154_alloc_msg (self, NL802154_CMD_SET_SHORT_ADDR, 0);
+	NLA_PUT_U16 (msg, NL802154_ATTR_SHORT_ADDR, htole16 (short_addr));
+	err = nl802154_send_and_recv (self, msg, NULL, NULL);
+	return err >= 0;
+
+nla_put_failure:
+	return FALSE;
+}
+
+/*****************************************************************************/
+
+static void
+nm_wpan_utils_init (NMWpanUtils *self)
+{
+}
+
+static void
+nm_wpan_utils_class_init (NMWpanUtilsClass *klass)
+{
+}
+
+NMWpanUtils *
+nm_wpan_utils_new (int ifindex, struct nl_sock *genl, gboolean check_scan)
+{
+	NMWpanUtils *self;
+	int id;
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+
+	if (!genl)
+		return NULL;
+
+	id = genl_ctrl_resolve (genl, "nl802154");
+	if (id < 0) {
+		_LOGD (LOGD_PLATFORM, "genl_ctrl_resolve: failed to resolve \"nl802154\"");
+		return NULL;
+	}
+
+	self = g_object_new (NM_TYPE_WPAN_UTILS, NULL);
+	self->ifindex = ifindex;
+	self->nl_sock = genl;
+	self->id = id;
+
+	return self;
+}
diff --git a/src/platform/wpan/nm-wpan-utils.h b/src/platform/wpan/nm-wpan-utils.h
new file mode 100644
index 00000000..f7d0c03e
--- /dev/null
+++ b/src/platform/wpan/nm-wpan-utils.h
@@ -0,0 +1,47 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __WPAN_UTILS_H__
+#define __WPAN_UTILS_H__
+
+#include <net/ethernet.h>
+
+#include "nm-dbus-interface.h"
+#include "platform/nm-netlink.h"
+
+typedef struct NMWpanUtils NMWpanUtils;
+
+#define NM_TYPE_WPAN_UTILS            (nm_wpan_utils_get_type ())
+#define NM_WPAN_UTILS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WPAN_UTILS, NMWpanUtils))
+#define NM_WPAN_UTILS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WPAN_UTILS, NMWpanUtilsClass))
+#define NM_IS_WPAN_UTILS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WPAN_UTILS))
+#define NM_IS_WPAN_UTILS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WPAN_UTILS))
+#define NM_WPAN_UTILS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WPAN_UTILS, NMWpanUtilsClass))
+
+GType nm_wpan_utils_get_type (void);
+
+NMWpanUtils *nm_wpan_utils_new (int ifindex, struct nl_sock *genl, gboolean check_scan);
+
+guint16 nm_wpan_utils_get_pan_id (NMWpanUtils *self);
+gboolean nm_wpan_utils_set_pan_id (NMWpanUtils *self, guint16 pan_id);
+
+guint16 nm_wpan_utils_get_short_addr (NMWpanUtils *self);
+gboolean nm_wpan_utils_set_short_addr (NMWpanUtils *self, guint16 short_addr);
+
+#endif  /* __WPAN_UTILS_H__ */