summary refs log tree commit diff
path: root/src/nm-connectivity.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-05-11 22:08:45 +0200
committerMichael Biebl <biebl@debian.org>2018-05-11 22:08:45 +0200
commitee9c73a923909e23a649407be77e25235d769e25 (patch)
treee21c923621fa278e737da693df9eb60ea31a6067 /src/nm-connectivity.c
parentf60117b41d5433be1b4a96d82cd11d0c3dce9b63 (diff)
New upstream version 1.10.8 upstream/1.10.8
Diffstat (limited to 'src/nm-connectivity.c')
-rw-r--r--src/nm-connectivity.c589
1 files changed, 218 insertions, 371 deletions
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index 389e72ad..8861f261 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -25,82 +25,22 @@
 #include "nm-connectivity.h"
 
 #include <string.h>
-
-#if WITH_CONCHECK
 #include <curl/curl.h>
-#endif
 
-#include "c-list/src/c-list.h"
 #include "nm-config.h"
 #include "NetworkManagerUtils.h"
 
-#define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n"
-
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_state_to_string, int /*NMConnectivityState*/,
-	NM_UTILS_LOOKUP_DEFAULT_WARN ("???"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_UNKNOWN,  "UNKNOWN"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_NONE,     "NONE"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_LIMITED,  "LIMITED"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_PORTAL,   "PORTAL"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FULL,     "FULL"),
-
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_ERROR,    "ERROR"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FAKE,     "FAKE"),
-);
-
-const char *
-nm_connectivity_state_to_string (NMConnectivityState state)
-{
-	return _state_to_string (state);
-}
-
-/*****************************************************************************/
-
-struct _NMConnectivityCheckHandle {
-	CList handles_lst;
-	NMConnectivity *self;
-	NMConnectivityCheckCallback callback;
-	gpointer user_data;
-
-	char *ifspec;
-
-#if WITH_CONCHECK
-	struct {
-		char *response;
-
-		CURL *curl_ehandle;
-		struct curl_slist *request_headers;
-
-		GString *recv_msg;
-	} concheck;
-#endif
-
-	guint timeout_id;
-};
-
-enum {
-	CONFIG_CHANGED,
-
-	LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
 typedef struct {
-	CList handles_lst_head;
 	char *uri;
 	char *response;
 	gboolean enabled;
 	guint interval;
 	NMConfig *config;
-#if WITH_CONCHECK
-	struct {
-		CURLM *curl_mhandle;
-		guint curl_timer;
-	} concheck;
-#endif
+	guint periodic_check_id;
+	CURLM *curl_mhandle;
+	guint curl_timer;
 } NMConnectivityPrivate;
 
 struct _NMConnectivity {
@@ -118,6 +58,14 @@ G_DEFINE_TYPE (NMConnectivity, nm_connectivity, G_TYPE_OBJECT)
 
 NM_DEFINE_SINGLETON_GETTER (NMConnectivity, nm_connectivity_get, NM_TYPE_CONNECTIVITY);
 
+enum {
+	PERIODIC_CHECK,
+
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
 /*****************************************************************************/
 
 #define _NMLOG_DOMAIN      LOGD_CONCHECK
@@ -130,172 +78,116 @@ NM_DEFINE_SINGLETON_GETTER (NMConnectivity, nm_connectivity_get, NM_TYPE_CONNECT
         \
         if (nm_logging_enabled (__level, _NMLOG2_DOMAIN)) { \
             _nm_log (__level, _NMLOG2_DOMAIN, 0, \
-                     (cb_data->ifspec ? &cb_data->ifspec[3] : NULL), \
-                     NULL, \
-                     "connectivity: (%s) " \
-                     _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                     (cb_data->ifspec ? &cb_data->ifspec[3] : "") \
-                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+                        &cb_data->ifspec[3], NULL, \
+                        "connectivity: (%s) " \
+                        _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                        &cb_data->ifspec[3] \
+                        _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
         } \
     } G_STMT_END
 
 /*****************************************************************************/
 
-static void
-cb_data_invoke_callback (NMConnectivityCheckHandle *cb_data,
-                         NMConnectivityState state,
-                         GError *error,
-                         const char *log_message)
-{
-	NMConnectivityCheckCallback callback;
-
-	nm_assert (cb_data);
-	nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
-
-	callback = cb_data->callback;
-	if (!callback)
-		return;
-
-	cb_data->callback = NULL;
-
-	nm_assert (log_message);
+NM_UTILS_LOOKUP_STR_DEFINE (nm_connectivity_state_to_string, NMConnectivityState,
+	NM_UTILS_LOOKUP_DEFAULT_WARN ("???"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_UNKNOWN,  "UNKNOWN"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_NONE,     "NONE"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_LIMITED,  "LIMITED"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_PORTAL,   "PORTAL"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FULL,     "FULL"),
+);
 
-	_LOG2D ("check completed: %s; %s",
-	        nm_connectivity_state_to_string (state),
-	        log_message);
+/*****************************************************************************/
 
-	callback (cb_data->self,
-	          cb_data,
-	          state,
-	          error,
-	          cb_data->user_data);
-}
+typedef struct {
+	GSimpleAsyncResult *simple;
+	char *response;
+	CURL *curl_ehandle;
+	size_t msg_size;
+	char *msg;
+	struct curl_slist *request_headers;
+	guint timeout_id;
+	char *ifspec;
+} ConCheckCbData;
 
 static void
-cb_data_free (NMConnectivityCheckHandle *cb_data,
-              NMConnectivityState state,
-              GError *error,
-              const char *log_message)
+finish_cb_data (ConCheckCbData *cb_data, NMConnectivityState new_state)
 {
-	NMConnectivity *self;
-
-	nm_assert (cb_data);
-
-	self = cb_data->self;
-
-	nm_assert (NM_IS_CONNECTIVITY (self));
-
-	c_list_unlink (&cb_data->handles_lst);
-
-#if WITH_CONCHECK
-	if (cb_data->concheck.curl_ehandle) {
-		NMConnectivityPrivate *priv;
-
-		/* Contrary to what cURL manual claim it is *not* safe to remove
-		 * the easy handle "at any moment"; specifically not from the
-		 * write function. Thus here we just dissociate the cb_data from
-		 * the easy handle and the easy handle will be cleaned up when the
-		 * message goes to CURLMSG_DONE in curl_check_connectivity(). */
-		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_WRITEFUNCTION, NULL);
-		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_WRITEDATA, NULL);
-		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_HEADERFUNCTION, NULL);
-		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_HEADERDATA, NULL);
-		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_PRIVATE, NULL);
-		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_HTTPHEADER, NULL);
-
-		priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-
-		curl_multi_remove_handle (priv->concheck.curl_mhandle, cb_data->concheck.curl_ehandle);
-		curl_easy_cleanup (cb_data->concheck.curl_ehandle);
-
-		curl_slist_free_all (cb_data->concheck.request_headers);
-	}
-#endif
-
-	nm_clear_g_source (&cb_data->timeout_id);
-
-	cb_data_invoke_callback (cb_data, state, error, log_message);
-
-#if WITH_CONCHECK
-	g_free (cb_data->concheck.response);
-	if (cb_data->concheck.recv_msg)
-		g_string_free (cb_data->concheck.recv_msg, TRUE);
-#endif
+	/* Contrary to what cURL manual claim it is *not* safe to remove
+	 * the easy handle "at any moment"; specifically not from the
+	 * write function. Thus here we just dissociate the cb_data from
+	 * the easy handle and the easy handle will be cleaned up when the
+	 * message goes to CURLMSG_DONE in curl_check_connectivity(). */
+	curl_easy_setopt (cb_data->curl_ehandle, CURLOPT_PRIVATE, NULL);
+
+	g_simple_async_result_set_op_res_gssize (cb_data->simple, new_state);
+	g_simple_async_result_complete (cb_data->simple);
+	g_object_unref (cb_data->simple);
+	curl_slist_free_all (cb_data->request_headers);
+	g_free (cb_data->response);
+	g_free (cb_data->msg);
 	g_free (cb_data->ifspec);
-	g_slice_free (NMConnectivityCheckHandle, cb_data);
-}
-
-/*****************************************************************************/
-
-#if WITH_CONCHECK
-static const char *
-_check_handle_get_response (NMConnectivityCheckHandle *cb_data)
-{
-	return cb_data->concheck.response ?: NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE;
+	g_source_remove (cb_data->timeout_id);
+	g_slice_free (ConCheckCbData, cb_data);
 }
 
 static void
-curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
+curl_check_connectivity (CURLM *mhandle, CURLMcode ret)
 {
-	NMConnectivityCheckHandle *cb_data;
+	ConCheckCbData *cb_data;
 	CURLMsg *msg;
 	CURLcode eret;
 	gint m_left;
-	long response_code;
-	CURLMcode ret;
-	int running_handles;
 
-	ret = curl_multi_socket_action (mhandle, sockfd, ev_bitmask, &running_handles);
 	if (ret != CURLM_OK)
-		_LOGE ("connectivity check failed: %d", ret);
+		_LOGW ("connectivity check failed");
 
 	while ((msg = curl_multi_info_read (mhandle, &m_left))) {
-
 		if (msg->msg != CURLMSG_DONE)
 			continue;
 
 		/* Here we have completed a session. Check easy session result. */
 		eret = curl_easy_getinfo (msg->easy_handle, CURLINFO_PRIVATE, (char **) &cb_data);
 		if (eret != CURLE_OK) {
-			_LOGE ("curl cannot extract cb_data for easy handle, skipping msg");
+			_LOG2E ("curl cannot extract cb_data for easy handle %p, skipping msg", msg->easy_handle);
 			continue;
 		}
 
-		if (!cb_data->callback) {
-			/* callback was already invoked earlier. */
-			cb_data_free (cb_data, NM_CONNECTIVITY_UNKNOWN, NULL, NULL);
-		} else if (msg->data.result != CURLE_OK) {
-			gs_free char *log_message = NULL;
-
-			log_message = g_strdup_printf ("check failed with curl status %d", msg->data.result);
-			cb_data_free (cb_data, NM_CONNECTIVITY_LIMITED, NULL,
-			              log_message);
-		} else if (   !((_check_handle_get_response (cb_data))[0])
-		           && (curl_easy_getinfo (msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code) == CURLE_OK)
-		           && response_code == 204) {
-			/* If we got a 204 response code (no content) and we actually
-			 * requested no content, report full connectivity. */
-			cb_data_free (cb_data, NM_CONNECTIVITY_FULL, NULL,
-			              "no content, as expected");
-		} else {
-			/* If we get here, it means that easy_write_cb() didn't read enough
-			 * bytes to be able to do a match, or that we were asking for no content
-			 * (204 response code) and we actually got some. Either way, that is
-			 * an indication of a captive portal */
-			cb_data_free (cb_data, NM_CONNECTIVITY_PORTAL, NULL,
-			              "unexpected short response");
+		if (cb_data) {
+			/* If cb_data is still there this message hasn't been
+			 * taken care of. Do so now. */
+			if (msg->data.result == CURLE_OK) {
+				/* If we get here, it means that easy_write_cb() didn't read enough
+				 * bytes to be able to do a match. */
+				_LOG2I ("response shorter than expected '%s'; assuming captive portal.",
+				        cb_data->response);
+				finish_cb_data (cb_data, NM_CONNECTIVITY_PORTAL);
+			} else {
+				_LOG2D ("check failed (%d)", msg->data.result);
+				finish_cb_data (cb_data, NM_CONNECTIVITY_LIMITED);
+			}
 		}
+
+		curl_multi_remove_handle (mhandle, msg->easy_handle);
+		curl_easy_cleanup (msg->easy_handle);
 	}
 }
 
 static gboolean
 curl_timeout_cb (gpointer user_data)
 {
-	gs_unref_object NMConnectivity *self = g_object_ref (NM_CONNECTIVITY (user_data));
+	NMConnectivity *self = NM_CONNECTIVITY (user_data);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	CURLMcode ret;
+	int pending_conn;
+
+	priv->curl_timer = 0;
+
+	ret = curl_multi_socket_action (priv->curl_mhandle, CURL_SOCKET_TIMEOUT, 0, &pending_conn);
+	_LOGT ("timeout elapsed - multi_socket_action (%d conn remaining)", pending_conn);
+
+	curl_check_connectivity (priv->curl_mhandle, ret);
 
-	priv->concheck.curl_timer = 0;
-	curl_check_connectivity (priv->concheck.curl_mhandle, CURL_SOCKET_TIMEOUT, 0);
 	return G_SOURCE_REMOVE;
 }
 
@@ -305,17 +197,21 @@ multi_timer_cb (CURLM *multi, long timeout_ms, void *userdata)
 	NMConnectivity *self = NM_CONNECTIVITY (userdata);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->concheck.curl_timer);
+	nm_clear_g_source (&priv->curl_timer);
 	if (timeout_ms != -1)
-		priv->concheck.curl_timer = g_timeout_add (timeout_ms, curl_timeout_cb, self);
+		priv->curl_timer = g_timeout_add (timeout_ms, curl_timeout_cb, self);
+
 	return 0;
 }
 
 static gboolean
-curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer user_data)
+curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer data)
 {
-	gs_unref_object NMConnectivity *self = g_object_ref (NM_CONNECTIVITY (user_data));
+	NMConnectivity *self = NM_CONNECTIVITY (data);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	CURLMcode ret;
+	int pending_conn = 0;
+	gboolean bret = TRUE;
 	int fd = g_io_channel_unix_get_fd (ch);
 	int action = 0;
 
@@ -323,11 +219,16 @@ curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer user_data)
 		action |= CURL_CSELECT_IN;
 	if (condition & G_IO_OUT)
 		action |= CURL_CSELECT_OUT;
-	if (condition & G_IO_ERR)
-		action |= CURL_CSELECT_ERR;
 
-	curl_check_connectivity (priv->concheck.curl_mhandle, fd, action);
-	return G_SOURCE_CONTINUE;
+	ret = curl_multi_socket_action (priv->curl_mhandle, fd, 0, &pending_conn);
+
+	curl_check_connectivity (priv->curl_mhandle, ret);
+
+	if (pending_conn == 0) {
+		nm_clear_g_source (&priv->curl_timer);
+		bret = FALSE;
+	}
+	return bret;
 }
 
 typedef struct {
@@ -340,12 +241,11 @@ multi_socket_cb (CURL *e_handle, curl_socket_t s, int what, void *userdata, void
 {
 	NMConnectivity *self = NM_CONNECTIVITY (userdata);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-	CurlSockData *fdp = socketp;
+	CurlSockData *fdp = (CurlSockData *) socketp;
 	GIOCondition condition = 0;
 
 	if (what == CURL_POLL_REMOVE) {
 		if (fdp) {
-			curl_multi_assign (priv->concheck.curl_mhandle, s, NULL);
 			nm_clear_g_source (&fdp->ev);
 			g_io_channel_unref (fdp->ch);
 			g_slice_free (CurlSockData, fdp);
@@ -354,7 +254,6 @@ multi_socket_cb (CURL *e_handle, curl_socket_t s, int what, void *userdata, void
 		if (!fdp) {
 			fdp = g_slice_new0 (CurlSockData);
 			fdp->ch = g_io_channel_unix_new (s);
-			curl_multi_assign (priv->concheck.curl_mhandle, s, fdp);
 		} else
 			nm_clear_g_source (&fdp->ev);
 
@@ -362,26 +261,29 @@ multi_socket_cb (CURL *e_handle, curl_socket_t s, int what, void *userdata, void
 			condition = G_IO_IN;
 		else if (what == CURL_POLL_OUT)
 			condition = G_IO_OUT;
-		else if (what == CURL_POLL_INOUT)
+		else if (condition == CURL_POLL_INOUT)
 			condition = G_IO_IN | G_IO_OUT;
 
 		if (condition)
 			fdp->ev = g_io_add_watch (fdp->ch, condition, curl_socketevent_cb, self);
+		curl_multi_assign (priv->curl_mhandle, s, fdp);
 	}
 
 	return CURLM_OK;
 }
 
+#define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n"
+
 static size_t
 easy_header_cb (char *buffer, size_t size, size_t nitems, void *userdata)
 {
-	NMConnectivityCheckHandle *cb_data = userdata;
+	ConCheckCbData *cb_data = userdata;
 	size_t len = size * nitems;
 
 	if (   len >= sizeof (HEADER_STATUS_ONLINE) - 1
 	    && !g_ascii_strncasecmp (buffer, HEADER_STATUS_ONLINE, sizeof (HEADER_STATUS_ONLINE) - 1)) {
-		cb_data_invoke_callback (cb_data, NM_CONNECTIVITY_FULL,
-		                         NULL, "status header found");
+		_LOG2D ("status header found, check successful");
+		finish_cb_data (cb_data, NM_CONNECTIVITY_FULL);
 		return 0;
 	}
 
@@ -391,26 +293,23 @@ easy_header_cb (char *buffer, size_t size, size_t nitems, void *userdata)
 static size_t
 easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
 {
-	NMConnectivityCheckHandle *cb_data = userdata;
+	ConCheckCbData *cb_data = userdata;
 	size_t len = size * nmemb;
-	const char *response = _check_handle_get_response (cb_data);;
-
-	if (!cb_data->concheck.recv_msg)
-		cb_data->concheck.recv_msg = g_string_sized_new (len + 10);
 
-	g_string_append_len (cb_data->concheck.recv_msg, buffer, len);
+	cb_data->msg = g_realloc (cb_data->msg, cb_data->msg_size + len);
+	memcpy (cb_data->msg + cb_data->msg_size, buffer, len);
+	cb_data->msg_size += len;
 
-	if (   response
-	    && cb_data->concheck.recv_msg->len >= strlen (response)) {
+	if (cb_data->msg_size >= strlen (cb_data->response)) {
 		/* We already have enough data -- check response */
-		if (g_str_has_prefix (cb_data->concheck.recv_msg->str, response)) {
-			cb_data_invoke_callback (cb_data, NM_CONNECTIVITY_FULL, NULL,
-			                         "expected response");
+		if (g_str_has_prefix (cb_data->msg, cb_data->response)) {
+			_LOG2D ("check successful.");
+			finish_cb_data (cb_data, NM_CONNECTIVITY_FULL);
 		} else {
-			cb_data_invoke_callback (cb_data, NM_CONNECTIVITY_PORTAL, NULL,
-			                         "unexpected response");
+			_LOG2I ("response did not match expected response '%s'; assuming captive portal.",
+			        cb_data->response);
+			finish_cb_data (cb_data, NM_CONNECTIVITY_PORTAL);
 		}
-
 		return 0;
 	}
 
@@ -418,139 +317,105 @@ easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
 }
 
 static gboolean
-_timeout_cb (gpointer user_data)
-{
-	NMConnectivityCheckHandle *cb_data = user_data;
-	NMConnectivity *self;
-
-	nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
-
-	self = cb_data->self;
-
-	nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (self)->handles_lst_head, &cb_data->handles_lst));
-
-	cb_data_free (cb_data, NM_CONNECTIVITY_LIMITED, NULL, "timeout");
-	return G_SOURCE_REMOVE;
-}
-#endif
-
-static gboolean
-_idle_cb (gpointer user_data)
+timeout_cb (gpointer user_data)
 {
-	NMConnectivityCheckHandle *cb_data = user_data;
-
-	nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
-	nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->handles_lst_head, &cb_data->handles_lst));
+	ConCheckCbData *cb_data = user_data;
+	NMConnectivity *self = NM_CONNECTIVITY (g_async_result_get_source_object (G_ASYNC_RESULT (cb_data->simple)));
+	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	CURL *ehandle = cb_data->curl_ehandle;
 
-	cb_data->timeout_id = 0;
-	if (!cb_data->ifspec) {
-		gs_free_error GError *error = NULL;
+	_LOG2I ("timed out");
+	finish_cb_data (cb_data, NM_CONNECTIVITY_LIMITED);
+	curl_multi_remove_handle (priv->curl_mhandle, ehandle);
+	curl_easy_cleanup (ehandle);
 
-		/* the invocation was with an invalid ifname. It is a fail. */
-		g_set_error (&error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT,
-		             "no interface specified for connectivity check");
-		cb_data_free (cb_data, NM_CONNECTIVITY_ERROR, NULL, "missing interface");
-	} else
-		cb_data_free (cb_data, NM_CONNECTIVITY_FAKE, NULL, "fake result");
 	return G_SOURCE_REMOVE;
 }
 
-NMConnectivityCheckHandle *
-nm_connectivity_check_start (NMConnectivity *self,
-                             const char *iface,
-                             NMConnectivityCheckCallback callback,
-                             gpointer user_data)
+void
+nm_connectivity_check_async (NMConnectivity      *self,
+                             const char          *iface,
+                             GAsyncReadyCallback  callback,
+                             gpointer             user_data)
 {
 	NMConnectivityPrivate *priv;
-	NMConnectivityCheckHandle *cb_data;
-
-	g_return_val_if_fail (NM_IS_CONNECTIVITY (self), NULL);
-	g_return_val_if_fail (!iface || iface[0], NULL);
-	g_return_val_if_fail (callback, NULL);
+	GSimpleAsyncResult *simple;
+	CURL *ehandle = NULL;
 
+	g_return_if_fail (NM_IS_CONNECTIVITY (self));
 	priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
-	cb_data = g_slice_new0 (NMConnectivityCheckHandle);
-	cb_data->self = self;
-	c_list_link_tail (&priv->handles_lst_head, &cb_data->handles_lst);
-	cb_data->callback = callback;
-	cb_data->user_data = user_data;
+	simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+	                                    nm_connectivity_check_async);
 
-	if (iface)
-		cb_data->ifspec = g_strdup_printf ("if!%s", iface);
+	if (priv->enabled)
+		ehandle = curl_easy_init ();
 
-#if WITH_CONCHECK
-	if (iface) {
-		CURL *ehandle;
-
-		if (   priv->enabled
-		    && (ehandle = curl_easy_init ())) {
-
-			cb_data->concheck.response = g_strdup (priv->response);
-			cb_data->concheck.curl_ehandle = ehandle;
-			cb_data->concheck.request_headers = curl_slist_append (NULL, "Connection: close");
-			curl_easy_setopt (ehandle, CURLOPT_URL, priv->uri);
-			curl_easy_setopt (ehandle, CURLOPT_WRITEFUNCTION, easy_write_cb);
-			curl_easy_setopt (ehandle, CURLOPT_WRITEDATA, cb_data);
-			curl_easy_setopt (ehandle, CURLOPT_HEADERFUNCTION, easy_header_cb);
-			curl_easy_setopt (ehandle, CURLOPT_HEADERDATA, cb_data);
-			curl_easy_setopt (ehandle, CURLOPT_PRIVATE, cb_data);
-			curl_easy_setopt (ehandle, CURLOPT_HTTPHEADER, cb_data->concheck.request_headers);
-			curl_easy_setopt (ehandle, CURLOPT_INTERFACE, cb_data->ifspec);
-			curl_multi_add_handle (priv->concheck.curl_mhandle, ehandle);
-
-			cb_data->timeout_id = g_timeout_add_seconds (20, _timeout_cb, cb_data);
-
-			_LOG2D ("start request to '%s'", priv->uri);
-			return cb_data;
-		}
+	if (ehandle) {
+		ConCheckCbData *cb_data = g_slice_new0 (ConCheckCbData);
+
+		cb_data->curl_ehandle = ehandle;
+		cb_data->request_headers = curl_slist_append (NULL, "Connection: close");
+		cb_data->ifspec = g_strdup_printf ("if!%s", iface);
+		cb_data->simple = simple;
+		if (priv->response)
+			cb_data->response = g_strdup (priv->response);
+		else
+			cb_data->response = g_strdup (NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE);
+
+		curl_easy_setopt (ehandle, CURLOPT_URL, priv->uri);
+		curl_easy_setopt (ehandle, CURLOPT_WRITEFUNCTION, easy_write_cb);
+		curl_easy_setopt (ehandle, CURLOPT_WRITEDATA, cb_data);
+		curl_easy_setopt (ehandle, CURLOPT_HEADERFUNCTION, easy_header_cb);
+		curl_easy_setopt (ehandle, CURLOPT_HEADERDATA, cb_data);
+		curl_easy_setopt (ehandle, CURLOPT_PRIVATE, cb_data);
+		curl_easy_setopt (ehandle, CURLOPT_HTTPHEADER, cb_data->request_headers);
+		curl_easy_setopt (ehandle, CURLOPT_INTERFACE, cb_data->ifspec);
+		curl_multi_add_handle (priv->curl_mhandle, ehandle);
+
+		cb_data->timeout_id = g_timeout_add_seconds (30, timeout_cb, cb_data);
+
+		_LOG2D ("sending request to '%s'", priv->uri);
+		return;
+	} else {
+		_LOGD ("(%s) faking request. Connectivity check disabled", iface);
 	}
-#endif
 
-	_LOG2D ("start fake request");
-	cb_data->timeout_id = g_idle_add (_idle_cb, cb_data);
-	return cb_data;
+	g_simple_async_result_set_op_res_gssize (simple, NM_CONNECTIVITY_UNKNOWN);
+	g_simple_async_result_complete_in_idle (simple);
+	g_object_unref (simple);
 }
 
-void
-nm_connectivity_check_cancel (NMConnectivityCheckHandle *cb_data)
+NMConnectivityState
+nm_connectivity_check_finish (NMConnectivity  *self,
+                              GAsyncResult    *result,
+                              GError         **error)
 {
-	NMConnectivity *self;
-	gs_free_error GError *error = NULL;
-
-	g_return_if_fail (cb_data);
-
-	self = cb_data->self;
+	GSimpleAsyncResult *simple;
 
-	g_return_if_fail (NM_IS_CONNECTIVITY (self));
-	g_return_if_fail (!c_list_is_empty (&cb_data->handles_lst));
-	g_return_if_fail (cb_data->callback);
-
-	nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (self)->handles_lst_head, &cb_data->handles_lst));
-
-	nm_utils_error_set_cancelled (&error, FALSE, "NMConnectivity");
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), nm_connectivity_check_async), NM_CONNECTIVITY_UNKNOWN);
 
-	cb_data_free (cb_data, NM_CONNECTIVITY_ERROR, error, "cancelled");
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	if (g_simple_async_result_propagate_error (simple, error))
+		return NM_CONNECTIVITY_UNKNOWN;
+	return (NMConnectivityState) g_simple_async_result_get_op_res_gssize (simple);
 }
 
-/*****************************************************************************/
-
 gboolean
 nm_connectivity_check_enabled (NMConnectivity *self)
 {
-	g_return_val_if_fail (NM_IS_CONNECTIVITY (self), FALSE);
+	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
-	return NM_CONNECTIVITY_GET_PRIVATE (self)->enabled;
+	return priv->enabled;
 }
 
 /*****************************************************************************/
 
-guint
-nm_connectivity_get_interval (NMConnectivity *self)
+static gboolean
+periodic_check (gpointer user_data)
 {
-	return nm_connectivity_check_enabled (self)
-	       ? NM_CONNECTIVITY_GET_PRIVATE (self)->interval
-	       : 0;
+	g_signal_emit (NM_CONNECTIVITY (user_data), signals[PERIODIC_CHECK], 0);
+	return G_SOURCE_CONTINUE;
 }
 
 static void
@@ -590,22 +455,18 @@ update_config (NMConnectivity *self, NMConfigData *config_data)
 
 	/* Set the interval. */
 	interval = nm_config_data_get_connectivity_interval (config_data);
-	interval = MIN (interval, (7 * 24 * 3600));
 	if (priv->interval != interval) {
 		priv->interval = interval;
 		changed = TRUE;
 	}
 
-	enabled = FALSE;
-#if WITH_CONCHECK
+	/* Set enabled flag. */
+	enabled = nm_config_data_get_connectivity_enabled (config_data);
 	/* connectivity checking also requires a valid URI, interval and
 	 * curl_mhandle */
-	if (   priv->uri
-	    && priv->interval
-	    && priv->concheck.curl_mhandle)
-		enabled = nm_config_data_get_connectivity_enabled (config_data);
-#endif
-
+	if (!(priv->uri && priv->interval && priv->curl_mhandle)) {
+		enabled = FALSE;
+	}
 	if (priv->enabled != enabled) {
 		priv->enabled = enabled;
 		changed = TRUE;
@@ -613,7 +474,7 @@ update_config (NMConnectivity *self, NMConfigData *config_data)
 
 	/* Set the response. */
 	response = nm_config_data_get_connectivity_response (config_data);
-	if (!nm_streq0 (response, priv->response)) {
+	if (g_strcmp0 (response, priv->response) != 0) {
 		/* a response %NULL means, NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE. Any other response
 		 * (including "") is accepted. */
 		g_free (priv->response);
@@ -621,8 +482,11 @@ update_config (NMConnectivity *self, NMConfigData *config_data)
 		changed = TRUE;
 	}
 
-	if (changed)
-		g_signal_emit (self, signals[CONFIG_CHANGED], 0);
+	if (changed) {
+		nm_clear_g_source (&priv->periodic_check_id);
+		if (nm_connectivity_check_enabled (self))
+			priv->periodic_check_id = g_timeout_add_seconds (priv->interval, periodic_check, self);
+	}
 }
 
 static void
@@ -635,37 +499,34 @@ config_changed_cb (NMConfig *config,
 	update_config (self, config_data);
 }
 
-/*****************************************************************************/
-
 static void
 nm_connectivity_init (NMConnectivity *self)
 {
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	CURLcode retv;
 
-	c_list_init (&priv->handles_lst_head);
+	retv = curl_global_init (CURL_GLOBAL_ALL);
+	if (retv == CURLE_OK)
+		priv->curl_mhandle = curl_multi_init ();
+
+	if (!priv->curl_mhandle)
+		 _LOGE ("unable to init cURL, connectivity check will not work");
+	else {
+		curl_multi_setopt (priv->curl_mhandle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
+		curl_multi_setopt (priv->curl_mhandle, CURLMOPT_SOCKETDATA, self);
+		curl_multi_setopt (priv->curl_mhandle, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
+		curl_multi_setopt (priv->curl_mhandle, CURLMOPT_TIMERDATA, self);
+		curl_multi_setopt (priv->curl_mhandle, CURLOPT_VERBOSE, 1);
+	}
 
 	priv->config = g_object_ref (nm_config_get ());
+
+	update_config (self, nm_config_get_data (priv->config));
 	g_signal_connect (G_OBJECT (priv->config),
 	                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
 	                  G_CALLBACK (config_changed_cb),
 	                  self);
 
-#if WITH_CONCHECK
-	if (curl_global_init (CURL_GLOBAL_ALL) == CURLE_OK)
-		priv->concheck.curl_mhandle = curl_multi_init ();
-
-	if (!priv->concheck.curl_mhandle)
-		 _LOGE ("unable to init cURL, connectivity check will not work");
-	else {
-		curl_multi_setopt (priv->concheck.curl_mhandle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
-		curl_multi_setopt (priv->concheck.curl_mhandle, CURLMOPT_SOCKETDATA, self);
-		curl_multi_setopt (priv->concheck.curl_mhandle, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
-		curl_multi_setopt (priv->concheck.curl_mhandle, CURLMOPT_TIMERDATA, self);
-		curl_multi_setopt (priv->concheck.curl_mhandle, CURLOPT_VERBOSE, 1);
-	}
-#endif
-
-	update_config (self, nm_config_get_data (priv->config));
 }
 
 static void
@@ -673,33 +534,19 @@ dispose (GObject *object)
 {
 	NMConnectivity *self = NM_CONNECTIVITY (object);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-	NMConnectivityCheckHandle *cb_data;
-	GError *error = NULL;
-
-again:
-	c_list_for_each_entry (cb_data, &priv->handles_lst_head, handles_lst) {
-		if (!error)
-			nm_utils_error_set_cancelled (&error, TRUE, "NMConnectivity");
-		cb_data_free (cb_data, NM_CONNECTIVITY_ERROR, error, "shutting down");
-		goto again;
-	}
-	g_clear_error (&error);
 
 	g_clear_pointer (&priv->uri, g_free);
 	g_clear_pointer (&priv->response, g_free);
 
-#if WITH_CONCHECK
-	nm_clear_g_source (&priv->concheck.curl_timer);
-
-	curl_multi_cleanup (priv->concheck.curl_mhandle);
-	curl_global_cleanup ();
-#endif
-
 	if (priv->config) {
 		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
 		g_clear_object (&priv->config);
 	}
 
+	curl_multi_cleanup (priv->curl_mhandle);
+	curl_global_cleanup ();
+	nm_clear_g_source (&priv->periodic_check_id);
+
 	G_OBJECT_CLASS (nm_connectivity_parent_class)->dispose (object);
 }
 
@@ -708,8 +555,8 @@ nm_connectivity_class_init (NMConnectivityClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-	signals[CONFIG_CHANGED] =
-	    g_signal_new (NM_CONNECTIVITY_CONFIG_CHANGED,
+	signals[PERIODIC_CHECK] =
+	    g_signal_new (NM_CONNECTIVITY_PERIODIC_CHECK,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_FIRST,
 	                  0, NULL, NULL, NULL,