summary refs log tree commit diff
path: root/src/nm-connectivity.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-connectivity.c')
-rw-r--r--src/nm-connectivity.c658
1 files changed, 345 insertions, 313 deletions
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index a33ec680..75bb7b63 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -17,6 +17,7 @@
  *
  * Copyright (C) 2011 Thomas Bechtold <thomasbechtold@jpberlin.de>
  * Copyright (C) 2011 Dan Williams <dcbw@redhat.com>
+ * Copyright (C) 2016,2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -24,36 +25,21 @@
 #include "nm-connectivity.h"
 
 #include <string.h>
-#if WITH_CONCHECK
-#include <libsoup/soup.h>
-#endif
+#include <curl/curl.h>
 
 #include "nm-config.h"
-#include "nm-dispatcher.h"
 #include "NetworkManagerUtils.h"
 
 /*****************************************************************************/
 
-NM_GOBJECT_PROPERTIES_DEFINE (NMConnectivity,
-	PROP_URI,
-	PROP_INTERVAL,
-	PROP_RESPONSE,
-	PROP_STATE,
-);
-
 typedef struct {
 	char *uri;
 	char *response;
 	guint interval;
-	gboolean online; /* whether periodic connectivity checking is enabled. */
-
-#if WITH_CONCHECK
-	SoupSession *soup_session;
-	gboolean initial_check_obsoleted;
-	guint check_id;
-#endif
-
-	NMConnectivityState state;
+	NMConfig *config;
+	guint periodic_check_id;
+	CURLM *curl_mhandle;
+	guint curl_timer;
 } NMConnectivityPrivate;
 
 struct _NMConnectivity {
@@ -69,20 +55,37 @@ G_DEFINE_TYPE (NMConnectivity, nm_connectivity, G_TYPE_OBJECT)
 
 #define NM_CONNECTIVITY_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMConnectivity, NM_IS_CONNECTIVITY)
 
+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
 #define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "connectivity", __VA_ARGS__)
 
-/*****************************************************************************/
-
-NMConnectivityState
-nm_connectivity_get_state (NMConnectivity *connectivity)
-{
-	g_return_val_if_fail (NM_IS_CONNECTIVITY (connectivity), NM_CONNECTIVITY_UNKNOWN);
+#define _NMLOG2_DOMAIN     LOGD_CONCHECK
+#define _NMLOG2(level, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        \
+        if (nm_logging_enabled (__level, _NMLOG2_DOMAIN)) { \
+            _nm_log (__level, _NMLOG2_DOMAIN, 0, \
+                        &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
 
-	return NM_CONNECTIVITY_GET_PRIVATE (connectivity)->state;
-}
+/*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE (nm_connectivity_state_to_string, NMConnectivityState,
 	NM_UTILS_LOOKUP_DEFAULT_WARN ("???"),
@@ -93,190 +96,245 @@ NM_UTILS_LOOKUP_STR_DEFINE (nm_connectivity_state_to_string, NMConnectivityState
 	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FULL,     "FULL"),
 );
 
-static void
-update_state (NMConnectivity *self, NMConnectivityState state)
-{
-	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-
-	if (priv->state != state) {
-		_LOGD ("state changed from %s to %s",
-		       nm_connectivity_state_to_string (priv->state),
-		       nm_connectivity_state_to_string (state));
-		priv->state = state;
-		_notify (self, PROP_STATE);
-
-		/* Notify dispatcher scripts of a connectivity state change */
-		nm_dispatcher_call_connectivity (DISPATCHER_ACTION_CONNECTIVITY_CHANGE, state);
-	}
-}
+/*****************************************************************************/
 
-#if WITH_CONCHECK
 typedef struct {
 	GSimpleAsyncResult *simple;
-	char *uri;
 	char *response;
-	guint check_id_when_scheduled;
+	CURL *curl_ehandle;
+	size_t msg_size;
+	char *msg;
+	struct curl_slist *request_headers;
+	guint timeout_id;
+	char *ifspec;
 } ConCheckCbData;
 
 static void
-nm_connectivity_check_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
+finish_cb_data (ConCheckCbData *cb_data, NMConnectivityState new_state)
 {
-	NMConnectivity *self;
-	NMConnectivityPrivate *priv;
-	ConCheckCbData *cb_data = user_data;
-	GSimpleAsyncResult *simple = cb_data->simple;
-	NMConnectivityState new_state;
-	const char *nm_header;
-	const char *uri = cb_data->uri;
-	const char *response = cb_data->response ? cb_data->response : NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE;
-
-	self = NM_CONNECTIVITY (g_async_result_get_source_object (G_ASYNC_RESULT (simple)));
-	/* it is safe to unref @self here, @simple holds yet another reference. */
-	g_object_unref (self);
-	priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	/* 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_source_remove (cb_data->timeout_id);
+	g_slice_free (ConCheckCbData, cb_data);
+}
 
-	if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
-		_LOGI ("check for uri '%s' failed with '%s'", uri, msg->reason_phrase);
-		new_state = NM_CONNECTIVITY_LIMITED;
-		goto done;
-	}
+static void
+curl_check_connectivity (CURLM *mhandle, CURLMcode ret)
+{
+	ConCheckCbData *cb_data;
+	CURLMsg *msg;
+	CURLcode eret;
+	gint m_left;
+
+	if (ret != CURLM_OK)
+		_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, &cb_data);
+		if (eret != CURLE_OK) {
+			_LOG2E ("curl cannot extract cb_data for easy handle %p, skipping msg", msg->easy_handle);
+			continue;
+		}
 
-	if (msg->status_code == 511) {
-		_LOGD ("check for uri '%s' returned status '%d %s'; captive portal present.",
-		       uri, msg->status_code, msg->reason_phrase);
-		new_state = NM_CONNECTIVITY_PORTAL;
-	} else {
-		/* Check headers; if we find the NM-specific one we're done */
-		nm_header = soup_message_headers_get_one (msg->response_headers, "X-NetworkManager-Status");
-		if (g_strcmp0 (nm_header, "online") == 0) {
-			_LOGD ("check for uri '%s' with Status header successful.", uri);
-			new_state = NM_CONNECTIVITY_FULL;
-		} else if (msg->status_code == SOUP_STATUS_OK) {
-			/* check response */
-			if (msg->response_body->data && g_str_has_prefix (msg->response_body->data, response)) {
-				_LOGD ("check for uri '%s' successful.", uri);
-				new_state = NM_CONNECTIVITY_FULL;
+		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 {
-				_LOGI ("check for uri '%s' did not match expected response '%s'; assuming captive portal.",
-					   uri, response);
-				new_state = NM_CONNECTIVITY_PORTAL;
+				_LOG2D ("check failed (%d)", msg->data.result);
+				finish_cb_data (cb_data, NM_CONNECTIVITY_LIMITED);
 			}
-		} else {
-			_LOGI ("check for uri '%s' returned status '%d %s'; assuming captive portal.",
-			       uri, msg->status_code, msg->reason_phrase);
-			new_state = NM_CONNECTIVITY_PORTAL;
 		}
-	}
 
- done:
-	/* Only update the state, if the call was done from external, or if the periodic check
-	 * is still the one that called this async check. */
-	if (!cb_data->check_id_when_scheduled || cb_data->check_id_when_scheduled == priv->check_id) {
-		/* Only update the state, if the URI and response parameters did not change
-		 * since invocation.
-		 * The interval does not matter for exernal calls, and for internal calls
-		 * we don't reach this line if the interval changed. */
-		if (   !g_strcmp0 (cb_data->uri, priv->uri)
-		    && !g_strcmp0 (cb_data->response, priv->response))
-			update_state (self, new_state);
+		curl_multi_remove_handle (mhandle, msg->easy_handle);
+		curl_easy_cleanup (msg->easy_handle);
 	}
+}
 
-	g_simple_async_result_set_op_res_gssize (simple, new_state);
-	g_simple_async_result_complete (simple);
-	g_object_unref (simple);
+static gboolean
+curl_timeout_cb (gpointer user_data)
+{
+	NMConnectivity *self = NM_CONNECTIVITY (user_data);
+	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	CURLMcode ret;
+	int pending_conn;
 
-	g_free (cb_data->uri);
-	g_free (cb_data->response);
-	g_slice_free (ConCheckCbData, cb_data);
-}
+	priv->curl_timer = 0;
 
-#define IS_PERIODIC_CHECK(callback)  (callback == run_check_complete)
+	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);
 
-static void
-run_check_complete (GObject      *object,
-                    GAsyncResult *result,
-                    gpointer      user_data)
-{
-	NMConnectivity *self = NM_CONNECTIVITY (object);
-	GError *error = NULL;
+	curl_check_connectivity (priv->curl_mhandle, ret);
 
-	nm_connectivity_check_finish (self, result, &error);
-	if (error) {
-		_LOGE ("check failed: %s", error->message);
-		g_error_free (error);
-	}
+	return G_SOURCE_REMOVE;
 }
 
-static gboolean
-run_check (gpointer user_data)
+static int
+multi_timer_cb (CURLM *multi, long timeout_ms, void *userdata)
 {
-	NMConnectivity *self = NM_CONNECTIVITY (user_data);
+	NMConnectivity *self = NM_CONNECTIVITY (userdata);
+	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
-	nm_connectivity_check_async (self, run_check_complete, NULL);
-	return TRUE;
+	nm_clear_g_source (&priv->curl_timer);
+	if (timeout_ms != -1)
+		priv->curl_timer = g_timeout_add (timeout_ms * 1000, curl_timeout_cb, self);
+
+	return 0;
 }
 
 static gboolean
-idle_start_periodic_checks (gpointer user_data)
+curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer data)
 {
-	NMConnectivity *self = 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;
+
+	if (condition & G_IO_IN)
+		action |= CURL_CSELECT_IN;
+	if (condition & G_IO_OUT)
+		action |= CURL_CSELECT_OUT;
 
-	priv->check_id = g_timeout_add_seconds (priv->interval, run_check, self);
-	if (!priv->initial_check_obsoleted)
-		run_check (self);
+	ret = curl_multi_socket_action (priv->curl_mhandle, fd, 0, &pending_conn);
 
-	return FALSE;
+	curl_check_connectivity (priv->curl_mhandle, ret);
+
+	if (pending_conn == 0) {
+		nm_clear_g_source (&priv->curl_timer);
+		bret = FALSE;
+	}
+	return bret;
 }
-#endif
 
-static void
-_reschedule_periodic_checks (NMConnectivity *self, gboolean force_reschedule)
+typedef struct {
+	GIOChannel *ch;
+	guint ev;
+} CurlSockData;
+
+static int
+multi_socket_cb (CURL *e_handle, curl_socket_t s, int what, void *userdata, void *socketp)
 {
+	NMConnectivity *self = NM_CONNECTIVITY (userdata);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-
-#if WITH_CONCHECK
-	if (priv->online && priv->uri && priv->interval) {
-		if (force_reschedule || !priv->check_id) {
-			if (priv->check_id)
-				g_source_remove (priv->check_id);
-			priv->check_id = g_timeout_add (0, idle_start_periodic_checks, self);
-			priv->initial_check_obsoleted = FALSE;
+	CurlSockData *fdp = (CurlSockData *) socketp;
+	GIOCondition condition = 0;
+
+	if (what == CURL_POLL_REMOVE) {
+		if (fdp) {
+			nm_clear_g_source (&fdp->ev);
+			g_io_channel_unref (fdp->ch);
+			g_slice_free (CurlSockData, fdp);
 		}
 	} else {
-		nm_clear_g_source (&priv->check_id);
+		if (!fdp) {
+			fdp = g_slice_new0 (CurlSockData);
+			fdp->ch = g_io_channel_unix_new (s);
+		} else
+			nm_clear_g_source (&fdp->ev);
+
+		if (what & CURL_POLL_IN)
+			condition |= G_IO_IN;
+		if (what & CURL_POLL_OUT)
+			condition |= G_IO_OUT;
+
+		fdp->ev = g_io_add_watch (fdp->ch, condition, curl_socketevent_cb, self);
+		curl_multi_assign (priv->curl_mhandle, s, fdp);
 	}
-	if (priv->check_id)
-		return;
-#endif
 
-	/* Either @online is %TRUE but we aren't checking connectivity, or
-	 * @online is %FALSE. Either way we can update our status immediately.
-	 */
-	update_state (self, priv->online ? NM_CONNECTIVITY_FULL : NM_CONNECTIVITY_NONE);
+	return CURLM_OK;
 }
 
-void
-nm_connectivity_set_online (NMConnectivity *self,
-                            gboolean        online)
+#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)
 {
-	NMConnectivityPrivate *priv= NM_CONNECTIVITY_GET_PRIVATE (self);
+	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)) {
+		_LOG2D ("status header found, check successful");
+		finish_cb_data (cb_data, NM_CONNECTIVITY_FULL);
+		return 0;
+	}
+
+	return len;
+}
 
-	online = !!online;
-	if (priv->online != online) {
-		_LOGD ("set %s", online ? "online" : "offline");
-		priv->online = online;
-		_reschedule_periodic_checks (self, FALSE);
+static size_t
+easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
+{
+	ConCheckCbData *cb_data = userdata;
+	size_t len = size * nmemb;
+
+	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 (cb_data->msg_size >= strlen (cb_data->response)) {
+		/* We already have enough data -- check response */
+		if (g_str_has_prefix (cb_data->msg, cb_data->response)) {
+			_LOG2D ("check successful.");
+			finish_cb_data (cb_data, NM_CONNECTIVITY_FULL);
+		} else {
+			_LOG2I ("response did not match expected response '%s'; assuming captive portal.",
+			        cb_data->response);
+			finish_cb_data (cb_data, NM_CONNECTIVITY_PORTAL);
+		}
+		return 0;
 	}
+
+	return len;
+}
+
+static gboolean
+timeout_cb (gpointer user_data)
+{
+	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;
+
+	_LOG2I ("timed out");
+	finish_cb_data (cb_data, NM_CONNECTIVITY_LIMITED);
+	curl_multi_remove_handle (priv->curl_mhandle, ehandle);
+	curl_easy_cleanup (ehandle);
+
+	return G_SOURCE_REMOVE;
 }
 
 void
 nm_connectivity_check_async (NMConnectivity      *self,
+                             const char          *iface,
                              GAsyncReadyCallback  callback,
                              gpointer             user_data)
 {
 	NMConnectivityPrivate *priv;
 	GSimpleAsyncResult *simple;
+	CURL *ehandle = NULL;
 
 	g_return_if_fail (NM_IS_CONNECTIVITY (self));
 	priv = NM_CONNECTIVITY_GET_PRIVATE (self);
@@ -284,39 +342,40 @@ nm_connectivity_check_async (NMConnectivity      *self,
 	simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
 	                                    nm_connectivity_check_async);
 
-#if WITH_CONCHECK
-	if (priv->uri && priv->interval) {
-		SoupMessage *msg;
-		ConCheckCbData *cb_data = g_slice_new (ConCheckCbData);
+	if (priv->uri && priv->interval && priv->curl_mhandle)
+		ehandle = curl_easy_init ();
+
+	if (ehandle) {
+		ConCheckCbData *cb_data = g_slice_new0 (ConCheckCbData);
 
-		msg = soup_message_new ("GET", priv->uri);
-		soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
-		/* Disable HTTP/1.1 keepalive; the connection should not persist */
-		soup_message_headers_append (msg->request_headers, "Connection", "close");
+		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;
-		cb_data->uri = g_strdup (priv->uri);
-		cb_data->response = g_strdup (priv->response);
+		if (priv->response)
+			cb_data->response = g_strdup (priv->response);
+		else
+			cb_data->response = g_strdup (NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE);
 
-		/* For internal calls (periodic), remember the check-id at time of scheduling. */
-		cb_data->check_id_when_scheduled = IS_PERIODIC_CHECK (callback) ? priv->check_id : 0;
+		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);
 
-		soup_session_queue_message (priv->soup_session,
-		                            msg,
-		                            nm_connectivity_check_cb,
-		                            cb_data);
-		priv->initial_check_obsoleted = TRUE;
+		cb_data->timeout_id = g_timeout_add_seconds (30, timeout_cb, cb_data);
 
-		_LOGD ("check: send %srequest to '%s'", IS_PERIODIC_CHECK (callback) ? "periodic " : "", priv->uri);
+		_LOG2D ("sending request to '%s'", priv->uri);
 		return;
 	} else {
-		g_warn_if_fail (!IS_PERIODIC_CHECK (callback));
-		_LOGD ("check: faking request. Connectivity check disabled");
+		_LOGD ("(%s) faking request. Connectivity check disabled", iface);
 	}
-#else
-	_LOGD ("check: faking request. Compiled without connectivity-check support");
-#endif
 
-	g_simple_async_result_set_op_res_gssize (simple, priv->state);
+	g_simple_async_result_set_op_res_gssize (simple, NM_CONNECTIVITY_UNKNOWN);
 	g_simple_async_result_complete_in_idle (simple);
 	g_object_unref (simple);
 }
@@ -336,120 +395,118 @@ nm_connectivity_check_finish (NMConnectivity  *self,
 	return (NMConnectivityState) g_simple_async_result_get_op_res_gssize (simple);
 }
 
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint property_id,
-              GValue *value, GParamSpec *pspec)
+gboolean
+nm_connectivity_check_enabled (NMConnectivity *self)
 {
-	NMConnectivity *self = NM_CONNECTIVITY (object);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
-	switch (property_id) {
-	case PROP_URI:
-		g_value_set_string (value, priv->uri);
-		break;
-	case PROP_INTERVAL:
-		g_value_set_uint (value, priv->interval);
-		break;
-	case PROP_RESPONSE:
-		if (priv->response)
-			g_value_set_string (value, priv->response);
-		else
-			g_value_set_static_string (value, NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE);
-		break;
-	case PROP_STATE:
-		g_value_set_uint (value, priv->state);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-		break;
-	}
+	return (priv->uri && priv->interval && priv->curl_mhandle);
+}
+
+/*****************************************************************************/
+
+static gboolean
+periodic_check (gpointer user_data)
+{
+	g_signal_emit (NM_CONNECTIVITY (user_data), signals[PERIODIC_CHECK], 0);
+	return G_SOURCE_CONTINUE;
 }
 
 static void
-set_property (GObject *object, guint property_id,
-              const GValue *value, GParamSpec *pspec)
+update_config (NMConnectivity *self, NMConfigData *config_data)
 {
-	NMConnectivity *self = NM_CONNECTIVITY (object);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 	const char *uri, *response;
 	guint interval;
-	gboolean changed;
-
-	switch (property_id) {
-	case PROP_URI:
-		uri = g_value_get_string (value);
-		if (uri && !*uri)
+	gboolean changed = FALSE;
+
+	/* Set the URI. */
+	uri = nm_config_data_get_connectivity_uri (config_data);
+	if (uri && !*uri)
+		uri = NULL;
+	changed = g_strcmp0 (uri, priv->uri) != 0;
+	if (uri) {
+		char *scheme = g_uri_parse_scheme (uri);
+
+		if (!scheme) {
+			_LOGE ("invalid URI '%s' for connectivity check.", uri);
+			uri = NULL;
+		} else if (strcasecmp (scheme, "https") == 0) {
+			_LOGW ("use of HTTPS for connectivity checking is not reliable and is discouraged (URI: %s)", uri);
+		} else if (strcasecmp (scheme, "http") != 0) {
+			_LOGE ("scheme of '%s' uri does't use a scheme that is allowed for connectivity check.", uri);
 			uri = NULL;
-		changed = g_strcmp0 (uri, priv->uri) != 0;
-#if WITH_CONCHECK
-		if (uri) {
-			SoupURI *soup_uri = soup_uri_new (uri);
-
-			if (!soup_uri || !SOUP_URI_VALID_FOR_HTTP (soup_uri)) {
-				_LOGE ("invalid uri '%s' for connectivity check.", uri);
-				uri = NULL;
-			}
-			if (uri && soup_uri && changed &&
-			    soup_uri_get_scheme(soup_uri) == SOUP_URI_SCHEME_HTTPS)
-				_LOGW ("use of HTTPS for connectivity checking is not reliable and is discouraged (URI: %s)", uri);
-			if (soup_uri)
-				soup_uri_free (soup_uri);
-		}
-#endif
-		if (changed) {
-			g_free (priv->uri);
-			priv->uri = g_strdup (uri);
-			_reschedule_periodic_checks (self, TRUE);
-		}
-		break;
-	case PROP_INTERVAL:
-		interval = g_value_get_uint (value);
-		if (priv->interval != interval) {
-			priv->interval = interval;
-			_reschedule_periodic_checks (self, TRUE);
-		}
-		break;
-	case PROP_RESPONSE:
-		response = g_value_get_string (value);
-		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);
-			priv->response = g_strdup (response);
-			_reschedule_periodic_checks (self, TRUE);
 		}
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-		break;
+
+		if (scheme)
+			g_free (scheme);
+	}
+	if (changed) {
+		g_free (priv->uri);
+		priv->uri = g_strdup (uri);
+	}
+
+	/* Set the interval. */
+	interval = nm_config_data_get_connectivity_interval (config_data);
+	if (priv->interval != interval) {
+		priv->interval = interval;
+		changed = TRUE;
+	}
+
+	/* Set the response. */
+	response = nm_config_data_get_connectivity_response (config_data);
+	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);
+		priv->response = g_strdup (response);
+		changed = TRUE;
+	}
+
+	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
+config_changed_cb (NMConfig *config,
+                   NMConfigData *config_data,
+                   NMConfigChangeFlags changes,
+                   NMConfigData *old_data,
+                   NMConnectivity *self)
+{
+	update_config (self, config_data);
+}
 
 static void
 nm_connectivity_init (NMConnectivity *self)
 {
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	CURLcode retv;
 
-#if WITH_CONCHECK
-	priv->soup_session = soup_session_async_new_with_options (SOUP_SESSION_TIMEOUT, 15, NULL);
-#endif
-	priv->state = NM_CONNECTIVITY_NONE;
-}
+	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);
 
-NMConnectivity *
-nm_connectivity_new (const char *uri,
-                     guint interval,
-                     const char *response)
-{
-	return g_object_new (NM_TYPE_CONNECTIVITY,
-	                     NM_CONNECTIVITY_URI, uri,
-	                     NM_CONNECTIVITY_INTERVAL, interval,
-	                     NM_CONNECTIVITY_RESPONSE, response,
-	                     NULL);
+	retv = curl_global_init (CURL_GLOBAL_ALL);
+	if (retv == CURLE_OK)
+		priv->curl_mhandle = curl_multi_init ();
+
+	if (priv->curl_mhandle == NULL) {
+		 _LOGE ("cnable to init cURL, connectivity check will not work");
+		return;
+	}
+
+	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);
 }
 
 static void
@@ -461,14 +518,14 @@ dispose (GObject *object)
 	g_clear_pointer (&priv->uri, g_free);
 	g_clear_pointer (&priv->response, g_free);
 
-#if WITH_CONCHECK
-	if (priv->soup_session) {
-		soup_session_abort (priv->soup_session);
-		g_clear_object (&priv->soup_session);
+	if (priv->config) {
+		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
+		g_clear_object (&priv->config);
 	}
 
-	nm_clear_g_source (&priv->check_id);
-#endif
+	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);
 }
@@ -478,37 +535,12 @@ nm_connectivity_class_init (NMConnectivityClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-	object_class->set_property = set_property;
-	object_class->get_property = get_property;
-	object_class->dispose = dispose;
+	signals[PERIODIC_CHECK] =
+	    g_signal_new (NM_CONNECTIVITY_PERIODIC_CHECK,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL, NULL,
+	                  G_TYPE_NONE, 0);
 
-	obj_properties[PROP_URI] =
-	     g_param_spec_string (NM_CONNECTIVITY_URI, "", "",
-	                          NULL,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_INTERVAL] =
-	     g_param_spec_uint (NM_CONNECTIVITY_INTERVAL, "", "",
-	                        0, G_MAXUINT, NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL,
-	                        G_PARAM_READWRITE |
-	                        G_PARAM_CONSTRUCT |
-	                        G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_RESPONSE] =
-	     g_param_spec_string (NM_CONNECTIVITY_RESPONSE, "", "",
-	                          NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_STATE] =
-	     g_param_spec_uint (NM_CONNECTIVITY_STATE, "", "",
-	                        NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
-	                        G_PARAM_READABLE |
-	                        G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+	object_class->dispose = dispose;
 }
-