summary refs log tree commit diff
path: root/src/platform/nm-linux-platform.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2016-03-01 16:55:22 +0100
committerMichael Biebl <biebl@debian.org>2016-03-01 16:55:22 +0100
commitc2de0d98ba39e0a1a970d066fd19be786092f376 (patch)
tree3838363c06a6019db6cf1f882ea34ebded63c38b /src/platform/nm-linux-platform.c
parent494f296a3baab08522617b24b1f126d8f9a17502 (diff)
Imported Upstream version 1.1.91 upstream/1.1.91
Diffstat (limited to 'src/platform/nm-linux-platform.c')
-rw-r--r--src/platform/nm-linux-platform.c295
1 files changed, 174 insertions, 121 deletions
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index f73c228b..196e75cb 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -17,7 +17,9 @@
  *
  * Copyright (C) 2012-2015 Red Hat, Inc.
  */
-#include "config.h"
+#include "nm-default.h"
+
+#include "nm-linux-platform.h"
 
 #include <errno.h>
 #include <unistd.h>
@@ -42,22 +44,21 @@
 #include <netlink/route/route.h>
 #include <gudev/gudev.h>
 
+#include "nm-utils.h"
 #include "nm-core-internal.h"
-#include "NetworkManagerUtils.h"
-#include "nm-linux-platform.h"
+#include "nm-setting-vlan.h"
+
+#include "nm-core-utils.h"
+#include "nmp-object.h"
 #include "nm-platform-utils.h"
-#include "NetworkManagerUtils.h"
-#include "nm-utils.h"
-#include "nm-default.h"
 #include "wifi/wifi-utils.h"
 #include "wifi/wifi-utils-wext.h"
-#include "nmp-object.h"
-
-/* This is only included for the translation of VLAN flags */
-#include "nm-setting-vlan.h"
 
 #define VLAN_FLAG_MVRP 0x8
 
+/* nm-internal error codes for libnl. Make sure they don't overlap. */
+#define _NLE_NM_NOBUFS 500
+
 /*********************************************************************************************/
 
 #define IFQDISCSIZ                      32
@@ -115,8 +116,26 @@
 #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 _NMLOG2(level, ...)               _LOG(level, _NMLOG2_DOMAIN, NULL,     __VA_ARGS__)
+#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; \
+        const void *const __self = (self); \
+        \
+        if (__self && __self != nm_platform_try_get ()) { \
+            g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
+            __p_prefix = __prefix; \
+        } \
+        _nm_log (__level, __domain, __errsv, \
+                 "%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 { \
@@ -124,20 +143,30 @@
         const NMLogDomain __domain = (domain); \
         \
         if (nm_logging_enabled (__level, __domain)) { \
-            char __prefix[32]; \
-            const char *__p_prefix = _NMLOG_PREFIX_NAME; \
-            const void *const __self = (self); \
+            _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); \
             \
-            if (__self && __self != nm_platform_try_get ()) { \
-                g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
-                __p_prefix = __prefix; \
-            } \
-            _nm_log (__level, __domain, 0, \
-                     "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                     __p_prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+            /* 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
 
+
 #define LOG_FMT_IP_TUNNEL "adding %s '%s' parent %u local %s remote %s"
 
 /******************************************************************
@@ -257,17 +286,6 @@ _support_user_ipv6ll_detect (struct nlattr **tb)
  * Various utilities
  ******************************************************************/
 
-const NMIPAddr nm_ip_addr_zero = NMIPAddrInit;
-
-#define IPV4LL_NETWORK (htonl (0xA9FE0000L))
-#define IPV4LL_NETMASK (htonl (0xFFFF0000L))
-
-static gboolean
-ip4_address_is_link_local (in_addr_t addr)
-{
-	return (addr & IPV4LL_NETMASK) == IPV4LL_NETWORK;
-}
-
 static guint
 _nm_ip_config_source_to_rtprot (NMIPConfigSource source)
 {
@@ -579,7 +597,7 @@ _lookup_cached_link (const NMPCache *cache, int ifindex, gboolean *completed_fro
 	if (!*completed_from_cache) {
 		obj = ifindex > 0 && cache ? nmp_cache_lookup_link (cache, ifindex) : NULL;
 
-		if (obj && !obj->_link.netlink.is_in_netlink)
+		if (obj && obj->_link.netlink.is_in_netlink)
 			*link_cached = obj;
 		else
 			*link_cached = NULL;
@@ -605,8 +623,8 @@ _linktype_read_devtype (const char *sysfs_path)
 		end = strpbrk (cont, "\r\n");
 		if (end)
 			*end++ = '\0';
-		if (strncmp (cont, DEVTYPE_PREFIX, STRLEN (DEVTYPE_PREFIX)) == 0) {
-			cont += STRLEN (DEVTYPE_PREFIX);
+		if (strncmp (cont, DEVTYPE_PREFIX, NM_STRLEN (DEVTYPE_PREFIX)) == 0) {
+			cont += NM_STRLEN (DEVTYPE_PREFIX);
 			memmove (contents, cont, strlen (cont) + 1);
 			return contents;
 		}
@@ -732,13 +750,19 @@ _linktype_get_type (NMPlatform *platform,
 		if (wifi_utils_is_wifi (ifname, sysfs_path))
 			return NM_LINK_TYPE_WIFI;
 
-		/* Standard wired ethernet interfaces don't report an rtnl_link_type, so
-		 * only allow fallback to Ethernet if no type is given.  This should
-		 * prevent future virtual network drivers from being treated as Ethernet
-		 * when they should be Generic instead.
-		 */
-		if (arptype == ARPHRD_ETHER && !kind && !devtype)
-			return NM_LINK_TYPE_ETHERNET;
+		if (arptype == ARPHRD_ETHER) {
+			/* Standard wired ethernet interfaces don't report an rtnl_link_type, so
+			 * only allow fallback to Ethernet if no type is given.  This should
+			 * prevent future virtual network drivers from being treated as Ethernet
+			 * when they should be Generic instead.
+			 */
+			if (!kind && !devtype)
+				return NM_LINK_TYPE_ETHERNET;
+			/* The USB gadget interfaces behave and look like ordinary ethernet devices
+			 * aside from the DEVTYPE. */
+			if (!g_strcmp0 (devtype, "gadget"))
+				return NM_LINK_TYPE_ETHERNET;
+		}
 	}
 
 	return NM_LINK_TYPE_UNKNOWN;
@@ -1464,8 +1488,8 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 		nl_info_data = li[IFLA_INFO_DATA];
 	}
 
-	obj->link.flags = ifi->ifi_flags;
-	obj->link.connected = NM_FLAGS_HAS (obj->link.flags, IFF_LOWER_UP);
+	obj->link.n_ifi_flags = ifi->ifi_flags;
+	obj->link.connected = NM_FLAGS_HAS (obj->link.n_ifi_flags, IFF_LOWER_UP);
 	obj->link.arptype = ifi->ifi_type;
 
 	obj->link.type = _linktype_get_type (platform,
@@ -1473,7 +1497,7 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	                                     nl_info_kind,
 	                                     obj->link.ifindex,
 	                                     obj->link.name,
-	                                     obj->link.flags,
+	                                     obj->link.n_ifi_flags,
 	                                     obj->link.arptype,
 	                                     completed_from_cache,
 	                                     &link_cached,
@@ -1650,9 +1674,9 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 	obj->ip_address.source = NM_IP_CONFIG_SOURCE_KERNEL;
 
 	if (!is_v4) {
-		obj->ip6_address.flags = tb[IFA_FLAGS]
-		                         ? nla_get_u32 (tb[IFA_FLAGS])
-		                         : ifa->ifa_flags;
+		obj->ip6_address.n_ifa_flags = tb[IFA_FLAGS]
+		                               ? nla_get_u32 (tb[IFA_FLAGS])
+		                               : ifa->ifa_flags;
 	}
 
 	if (is_v4) {
@@ -2329,8 +2353,8 @@ static gboolean
 _support_kernel_extended_ifa_flags_get (void)
 {
 	if (_support_kernel_extended_ifa_flags_still_undecided ()) {
-		_LOG2W ("support: kernel-extended-ifa-flags: unable to detect kernel support for handling IPv6 temporary addresses. Assume none");
-		_support_kernel_extended_ifa_flags = 0;
+		_LOG2W ("support: kernel-extended-ifa-flags: unable to detect kernel support for handling IPv6 temporary addresses. Assume support");
+		_support_kernel_extended_ifa_flags = 1;
 	}
 	return _support_kernel_extended_ifa_flags;
 }
@@ -2427,8 +2451,11 @@ _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *path, const char *va
 static gboolean
 sysctl_set (NMPlatform *platform, const char *path, const char *value)
 {
-	int fd, len, nwrote, tries;
+	int fd, tries;
+	gssize nwrote;
+	gsize len;
 	char *actual;
+	gs_free char *actual_free = NULL;
 
 	g_return_val_if_fail (path != NULL, FALSE);
 	g_return_val_if_fail (value != NULL, FALSE);
@@ -2458,10 +2485,16 @@ sysctl_set (NMPlatform *platform, const char *path, const char *value)
 	 * sysctl support partial writes so the LF must be added to the string we're
 	 * about to write.
 	 */
-	actual = g_strdup_printf ("%s\n", value);
+	len = strlen (value) + 1;
+	if (len > 512)
+		actual = actual_free = g_malloc (len + 1);
+	else
+		actual = g_alloca (len + 1);
+	memcpy (actual, value, len - 1);
+	actual[len - 1] = '\n';
+	actual[len] = '\0';
 
 	/* Try to write the entire value three times if a partial write occurs */
-	len = strlen (actual);
 	for (tries = 0, nwrote = 0; tries < 3 && nwrote != len; tries++) {
 		nwrote = write (fd, actual, len);
 		if (nwrote == -1) {
@@ -2480,15 +2513,14 @@ sysctl_set (NMPlatform *platform, const char *path, const char *value)
 		       path, value);
 	}
 
-	g_free (actual);
 	close (fd);
 	return (nwrote == len);
 }
 
 static GSList *sysctl_clear_cache_list;
 
-void
-_nm_linux_platform_sysctl_clear_cache (void)
+static void
+_nm_logging_clear_platform_logging_cache_impl (void)
 {
 	while (sysctl_clear_cache_list) {
 		NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (sysctl_clear_cache_list->data);
@@ -2508,6 +2540,7 @@ _log_dbg_sysctl_get_impl (NMPlatform *platform, const char *path, const char *co
 	const char *prev_value = NULL;
 
 	if (!priv->sysctl_get_prev_values) {
+		_nm_logging_clear_platform_logging_cache = _nm_logging_clear_platform_logging_cache_impl;
 		sysctl_clear_cache_list = g_slist_prepend (sysctl_clear_cache_list, platform);
 		priv->sysctl_get_prev_values = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 	} else
@@ -2798,13 +2831,23 @@ delayed_action_wait_for_nl_response_complete (NMPlatform *platform,
 
 static void
 delayed_action_wait_for_nl_response_complete_all (NMPlatform *platform,
-                                                  WaitForNlResponseResult result)
+                                                  WaitForNlResponseResult fallback_result)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 
 	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
-		while (priv->delayed_action.list_wait_for_nl_response->len > 0)
-			delayed_action_wait_for_nl_response_complete (platform, priv->delayed_action.list_wait_for_nl_response->len - 1, result);
+		while (priv->delayed_action.list_wait_for_nl_response->len > 0) {
+			const DelayedActionWaitForNlResponseData *data;
+			guint idx = priv->delayed_action.list_wait_for_nl_response->len - 1;
+			WaitForNlResponseResult r;
+
+			data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, idx);
+
+			/* prefer the result that we already have. */
+			r = data->seq_result ? : fallback_result;
+
+			delayed_action_wait_for_nl_response_complete (platform, idx, r);
+		}
 	}
 	nm_assert (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
 	nm_assert (priv->delayed_action.list_wait_for_nl_response->len == 0);
@@ -3190,9 +3233,9 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 			if (   ops_type == NMP_CACHE_OPS_UPDATED
 			    && old && new /* <-- nonsensical, make coverity happy */
 			    && old->_link.netlink.is_in_netlink
-			    && NM_FLAGS_HAS (old->link.flags, IFF_LOWER_UP)
+			    && NM_FLAGS_HAS (old->link.n_ifi_flags, IFF_LOWER_UP)
 			    && new->_link.netlink.is_in_netlink
-			    && !NM_FLAGS_HAS (new->link.flags, IFF_LOWER_UP)) {
+			    && !NM_FLAGS_HAS (new->link.n_ifi_flags, IFF_LOWER_UP)) {
 				delayed_action_schedule (platform,
 				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
 				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
@@ -3235,6 +3278,19 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 				                         DELAYED_ACTION_TYPE_REFRESH_LINK,
 				                         GINT_TO_POINTER (new->link.ifindex));
 			}
+			if (   new->link.type == NM_LINK_TYPE_ETHERNET
+			    && new->link.addr.len == 0) {
+				/* Due to a kernel bug, we sometimes receive spurious NEWLINK
+				 * messages after a wifi interface has disappeared. Since the
+				 * link is not present anymore we can't determine its type and
+				 * thus it will show up as a Ethernet one, with no address
+				 * specified.  Request the link again to check if it really
+				 * exists.  https://bugzilla.redhat.com/show_bug.cgi?id=1302037
+				 */
+				delayed_action_schedule (platform,
+				                         DELAYED_ACTION_TYPE_REFRESH_LINK,
+				                         GINT_TO_POINTER (new->link.ifindex));
+			}
 		}
 		{
 			/* on enslave/release, we also refresh the master. */
@@ -3243,8 +3299,8 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 
 			changed_master =    (new && new->_link.netlink.is_in_netlink && new->link.master > 0 ? new->link.master : 0)
 			                 != (old && old->_link.netlink.is_in_netlink && old->link.master > 0 ? old->link.master : 0);
-			changed_connected =    (new && new->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (new->link.flags, IFF_LOWER_UP) : 2)
-			                    != (old && old->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (old->link.flags, IFF_LOWER_UP) : 2);
+			changed_connected =    (new && new->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (new->link.n_ifi_flags, IFF_LOWER_UP) : 2)
+			                    != (old && old->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (old->link.n_ifi_flags, IFF_LOWER_UP) : 2);
 
 			if (changed_master || changed_connected) {
 				ifindex1 = (old && old->_link.netlink.is_in_netlink && old->link.master > 0) ? old->link.master : 0;
@@ -3455,7 +3511,7 @@ event_seq_check (NMPlatform *platform, struct nl_msg *msg, WaitForNlResponseResu
 }
 
 static void
-event_valid_msg (NMPlatform *platform, struct nl_msg *msg)
+event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_events)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	nm_auto_nmpobj NMPObject *obj = NULL;
@@ -3471,6 +3527,9 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg)
 	if (_support_kernel_extended_ifa_flags_still_undecided () && msghdr->nlmsg_type == RTM_NEWADDR)
 		_support_kernel_extended_ifa_flags_detect (msg);
 
+	if (!handle_events)
+		return;
+
 	if (NM_IN_SET (msghdr->nlmsg_type, RTM_DELLINK, RTM_DELADDR, RTM_DELROUTE)) {
 		/* The event notifies about a deleted object. We don't need to initialize all
 		 * fields of the object. */
@@ -4684,7 +4743,6 @@ link_vlan_change (NMPlatform *platform,
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	const NMPObject *obj_cache;
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	unsigned flags;
 	const NMPObjectLnkVlan *lnk;
 	guint new_n_ingress_map = 0;
 	guint new_n_egress_map = 0;
@@ -4702,7 +4760,6 @@ link_vlan_change (NMPlatform *platform,
 	}
 
 	lnk = obj_cache->_link.netlink.lnk ? &obj_cache->_link.netlink.lnk->_lnk_vlan : NULL;
-	flags = obj_cache->link.flags;
 
 	flags_set &= flags_mask;
 
@@ -4821,6 +4878,7 @@ tun_add (NMPlatform *platform, const char *name, gboolean tap,
 	if (out_link)
 		*out_link = obj ? &obj->link : NULL;
 
+	close (fd);
 	return !!obj;
 }
 
@@ -5139,7 +5197,7 @@ ip4_address_add (NMPlatform *platform,
 	                             plen,
 	                             &peer_addr,
 	                             0,
-	                             ip4_address_is_link_local (addr) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE,
+	                             nmp_utils_ip4_address_is_link_local (addr) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE,
 	                             lifetime,
 	                             preferred,
 	                             label);
@@ -5156,7 +5214,7 @@ ip6_address_add (NMPlatform *platform,
                  struct in6_addr peer_addr,
                  guint32 lifetime,
                  guint32 preferred,
-                 guint flags)
+                 guint32 flags)
 {
 	NMPObject obj_id;
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -5492,8 +5550,7 @@ event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	struct nl_sock *sk = priv->nlh;
-	int n, err = 0, multipart = 0, interrupted = 0, nrecv = 0;
-	unsigned char *buf = NULL;
+	int n, err = 0, multipart = 0, interrupted = 0;
 	struct nlmsghdr *hdr;
 	WaitForNlResponseResult seq_result;
 
@@ -5503,34 +5560,44 @@ event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events)
 	initialize the variable. Thomas Graf.
 	*/
 	struct sockaddr_nl nla = {0};
-	struct nl_msg *msg = NULL;
-	struct ucred *creds = NULL;
+	nm_auto_free struct ucred *creds = NULL;
+	nm_auto_free unsigned char *buf = NULL;
 
 continue_reading:
+	g_clear_pointer (&buf, free);
+	g_clear_pointer (&creds, free);
 	errno = 0;
 	n = nl_recv (sk, &nla, &buf, &creds);
 
-	/* Work around a libnl bug fixed in 3.2.22 (375a6294) */
-	if (n == 0 && errno == EAGAIN) {
-		/* EAGAIN is equal to EWOULDBLOCK. If it would not be, we'd have to
-		 * workaround libnl3 mapping EWOULDBLOCK to -NLE_FAILURE. */
-		G_STATIC_ASSERT (EAGAIN == EWOULDBLOCK);
-		n = -NLE_AGAIN;
+	switch (n) {
+	case 0:
+		/* Work around a libnl bug fixed in 3.2.22 (375a6294) */
+		if (errno == EAGAIN) {
+			/* EAGAIN is equal to EWOULDBLOCK. If it would not be, we'd have to
+			 * workaround libnl3 mapping EWOULDBLOCK to -NLE_FAILURE. */
+			G_STATIC_ASSERT (EAGAIN == EWOULDBLOCK);
+			n = -NLE_AGAIN;
+		}
+		break;
+	case -NLE_NOMEM:
+		if (errno == ENOBUFS) {
+			/* we are very much interested in a overrun of the receive buffer.
+			 * nl_recv() maps all kinds of errors to NLE_NOMEM, so check also
+			 * for errno explicitly. And if so, hack our own return code to signal
+			 * the overrun. */
+			n = -_NLE_NM_NOBUFS;
+		}
+		break;
 	}
 
 	if (n <= 0)
 		return n;
 
-	if (!handle_events) {
-		/* we read until failure or there is nothing to read (EAGAIN). */
-		goto continue_reading;
-	}
-
 	hdr = (struct nlmsghdr *) buf;
 	while (nlmsg_ok (hdr, n)) {
+		nm_auto_nlmsg struct nl_msg *msg = NULL;
 		gboolean abort_parsing = FALSE;
 
-		nlmsg_free (msg);
 		msg = nlmsg_convert (hdr);
 		if (!msg) {
 			err = -NLE_NOMEM;
@@ -5539,13 +5606,13 @@ continue_reading:
 
 		nlmsg_set_proto (msg, NETLINK_ROUTE);
 		nlmsg_set_src (msg, &nla);
-		nrecv++;
 
 		if (!creds || creds->pid) {
 			if (creds)
-				_LOGD ("netlink: recvmsg: received non-kernel message (pid %d)", creds->pid);
+				_LOGT ("netlink: recvmsg: received non-kernel message (pid %d)", creds->pid);
 			else
-				_LOGD ("netlink: recvmsg: received message without credentials");
+				_LOGT ("netlink: recvmsg: received message without credentials");
+			err = 0;
 			goto stop;
 		}
 
@@ -5618,42 +5685,35 @@ continue_reading:
 			/* Valid message (not checking for MULTIPART bit to
 			 * get along with broken kernels. NL_SKIP has no
 			 * effect on this.  */
-			event_valid_msg (platform, msg);
+
+			event_valid_msg (platform, msg, handle_events);
+
 			seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
 		}
 
 		event_seq_check (platform, msg, seq_result);
-		err = 0;
-		hdr = nlmsg_next (hdr, &n);
 
 		if (abort_parsing)
-			goto out;
-	}
+			goto stop;
 
-	nlmsg_free (msg);
-	free (buf);
-	free (creds);
-	buf = NULL;
-	msg = NULL;
-	creds = NULL;
+		err = 0;
+		hdr = nlmsg_next (hdr, &n);
+	}
 
 	if (multipart) {
 		/* Multipart message not yet complete, continue reading */
 		goto continue_reading;
 	}
 stop:
-	err = 0;
+	if (!handle_events) {
+		/* when we don't handle events, we want to drain all messages from the socket
+		 * without handling the messages (but still check for sequence numbers).
+		 * Repeat reading. */
+		goto continue_reading;
+	}
 out:
-	nlmsg_free (msg);
-	free (buf);
-	free (creds);
-
 	if (interrupted)
 		err = -NLE_DUMP_INTR;
-
-	if (!err)
-		err = nrecv;
-
 	return err;
 }
 
@@ -5687,12 +5747,10 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 				case -NLE_DUMP_INTR:
 					_LOGD ("netlink: read: uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
 					break;
-				case -NLE_NOMEM:
+				case -_NLE_NM_NOBUFS:
 					_LOGI ("netlink: read: too many netlink events. Need to resynchronize platform cache");
-					/* Drain the event queue, we've lost events and are out of sync anyway and we'd
-					 * like to free up some space. We'll read in the status synchronously. */
-					delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC);
 					event_handler_recvmsgs (platform, FALSE);
+					delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC);
 					delayed_action_schedule (platform,
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
@@ -5728,9 +5786,10 @@ after_read:
 				i++;
 
 				if (   data_next.seq_number == 0
-				    || data_next.timeout_abs_ns > data->timeout_abs_ns)
+				    || data_next.timeout_abs_ns > data->timeout_abs_ns) {
 					data_next.seq_number = data->seq_number;
 					data_next.timeout_abs_ns = data->timeout_abs_ns;
+				}
 			}
 		}
 
@@ -5916,14 +5975,8 @@ constructed (GObject *_object)
 	nle = nl_socket_set_nonblocking (priv->nlh);
 	g_assert (!nle);
 
-	/* The default buffer size wasn't enough for the testsuites. It might just
-	 * as well happen with NetworkManager itself. For now let's hope 128KB is
-	 * good enough.
-	 *
-	 * FIXME: it's unclear that this is still actually needed. The testsuite
-	 * certainly doesn't fail for me. Maybe it can be removed.
-	 */
-	nle = nl_socket_set_buffer_size (priv->nlh, 131072, 0);
+	/* use 8 MB for receive socket kernel queue. */
+	nle = nl_socket_set_buffer_size (priv->nlh, 8*1024*1024, 0);
 	g_assert (!nle);
 
 	nle = nl_socket_add_memberships (priv->nlh,