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.c677
1 files changed, 510 insertions, 167 deletions
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index 8a6e955a..b72413d2 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -17,22 +17,23 @@
  *
  * Copyright (C) 2011 Thomas Bechtold <thomasbechtold@jpberlin.de>
  * Copyright (C) 2011 Dan Williams <dcbw@redhat.com>
- * Copyright (C) 2016,2017 Red Hat, Inc.
+ * Copyright (C) 2016 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
 
 #include "nm-connectivity.h"
 
-#include <string.h>
-
 #if WITH_CONCHECK
 #include <curl/curl.h>
 #endif
 
 #include "c-list/src/c-list.h"
+#include "nm-core-internal.h"
 #include "nm-config.h"
 #include "NetworkManagerUtils.h"
+#include "nm-dbus-manager.h"
+#include "dns/nm-dns-manager.h"
 
 #define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n"
 
@@ -60,6 +61,14 @@ nm_connectivity_state_to_string (NMConnectivityState state)
 
 /*****************************************************************************/
 
+typedef struct {
+	guint ref_count;
+	char *uri;
+	char *host;
+	char *port;
+	char *response;
+} ConConfig;
+
 struct _NMConnectivityCheckHandle {
 	CList handles_lst;
 	NMConnectivity *self;
@@ -68,22 +77,35 @@ struct _NMConnectivityCheckHandle {
 
 	char *ifspec;
 
+	const char *completed_log_message;
+	char *completed_log_message_free;
+
 #if WITH_CONCHECK
 	struct {
-		char *response;
+		ConConfig *con_config;
 
+		GCancellable *resolve_cancellable;
+		CURLM *curl_mhandle;
 		CURL *curl_ehandle;
 		struct curl_slist *request_headers;
+		struct curl_slist *hosts;
+
+		gsize response_good_cnt;
 
-		GString *recv_msg;
+		guint curl_timer;
+		int ch_ifindex;
 	} concheck;
 #endif
 
-	const char *completed_log_message;
-	char *completed_log_message_free;
-	NMConnectivityState completed_state;
+	guint64 request_counter;
+
+	int addr_family;
 
 	guint timeout_id;
+
+	NMConnectivityState completed_state;
+
+	bool fail_reason_no_dbus_connection:1;
 };
 
 enum {
@@ -97,17 +119,12 @@ static guint signals[LAST_SIGNAL] = { 0 };
 typedef struct {
 	CList handles_lst_head;
 	CList completed_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
+	ConConfig *con_config;
+	guint interval;
+
+	bool enabled:1;
+	bool uri_valid:1;
 } NMConnectivityPrivate;
 
 struct _NMConnectivity {
@@ -139,15 +156,53 @@ NM_DEFINE_SINGLETON_GETTER (NMConnectivity, nm_connectivity_get, NM_TYPE_CONNECT
             _nm_log (__level, _NMLOG2_DOMAIN, 0, \
                      (cb_data->ifspec ? &cb_data->ifspec[3] : NULL), \
                      NULL, \
-                     "connectivity: (%s) " \
+                     "connectivity: (%s,IPv%c,%"G_GUINT64_FORMAT") " \
                      _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                     (cb_data->ifspec ? &cb_data->ifspec[3] : "") \
+                     (cb_data->ifspec ? &cb_data->ifspec[3] : ""), \
+                     nm_utils_addr_family_to_char (cb_data->addr_family), \
+                     cb_data->request_counter \
                      _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
         } \
     } G_STMT_END
 
 /*****************************************************************************/
 
+static ConConfig *
+_con_config_ref (ConConfig *con_config)
+{
+	if (con_config) {
+		nm_assert (con_config->ref_count > 0);
+		++con_config->ref_count;
+	}
+	return con_config;
+}
+
+static void
+_con_config_unref (ConConfig *con_config)
+{
+	if (!con_config)
+		return;
+
+	nm_assert (con_config->ref_count > 0);
+
+	if (--con_config->ref_count != 0)
+		return;
+
+	g_free (con_config->uri);
+	g_free (con_config->host);
+	g_free (con_config->port);
+	g_free (con_config->response);
+	g_slice_free (ConConfig, con_config);
+}
+
+static const char *
+_con_config_get_response (const ConConfig *con_config)
+{
+	return con_config->response ?: NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE;
+}
+
+/*****************************************************************************/
+
 static void
 cb_data_complete (NMConnectivityCheckHandle *cb_data,
                   NMConnectivityState state,
@@ -171,8 +226,6 @@ cb_data_complete (NMConnectivityCheckHandle *cb_data,
 
 #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 it's not safe to
 		 * remove *any* handle from within a libcurl callback. That is
@@ -187,13 +240,16 @@ cb_data_complete (NMConnectivityCheckHandle *cb_data,
 		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_multi_remove_handle (cb_data->concheck.curl_mhandle,
+		                          cb_data->concheck.curl_ehandle);
 		curl_easy_cleanup (cb_data->concheck.curl_ehandle);
+		curl_multi_cleanup (cb_data->concheck.curl_mhandle);
 
 		curl_slist_free_all (cb_data->concheck.request_headers);
+		curl_slist_free_all (cb_data->concheck.hosts);
 	}
+	nm_clear_g_source (&cb_data->concheck.curl_timer);
+	nm_clear_g_cancellable (&cb_data->concheck.resolve_cancellable);
 #endif
 
 	nm_clear_g_source (&cb_data->timeout_id);
@@ -212,9 +268,7 @@ cb_data_complete (NMConnectivityCheckHandle *cb_data,
 	 * not use the self pointer too. */
 
 #if WITH_CONCHECK
-	g_free (cb_data->concheck.response);
-	if (cb_data->concheck.recv_msg)
-		g_string_free (cb_data->concheck.recv_msg, TRUE);
+	_con_config_unref (cb_data->concheck.con_config);
 #endif
 	g_free (cb_data->ifspec);
 	if (cb_data->completed_log_message_free)
@@ -265,12 +319,6 @@ _complete_queued (NMConnectivity *self)
 	nm_g_object_unref (self_keep_alive);
 }
 
-static const char *
-_check_handle_get_response (NMConnectivityCheckHandle *cb_data)
-{
-	return cb_data->concheck.response ?: NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE;
-}
-
 static gboolean
 _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 {
@@ -285,11 +333,12 @@ _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 
 	ret = curl_multi_socket_action (mhandle, sockfd, ev_bitmask, &running_handles);
 	if (ret != CURLM_OK) {
-		_LOGD ("connectivity check failed: %d", ret);
+		_LOGD ("connectivity check failed: (%d) %s", ret, curl_easy_strerror (ret));
 		success = FALSE;
 	}
 
 	while ((msg = curl_multi_info_read (mhandle, &m_left))) {
+		const char *response;
 
 		if (msg->msg != CURLMSG_DONE)
 			continue;
@@ -297,7 +346,8 @@ _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 		/* 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) {
-			_LOGD ("curl cannot extract cb_data for easy handle, skipping msg");
+			_LOGD ("curl cannot extract cb_data for easy handle, skipping msg: (%d) %s",
+			       eret, curl_easy_strerror (eret));
 			success = FALSE;
 			continue;
 		}
@@ -314,26 +364,47 @@ _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 			cb_data_queue_completed (cb_data,
 			                         NM_CONNECTIVITY_LIMITED,
 			                         NULL,
-			                         g_strdup_printf ("check failed with curl status %d", msg->data.result));
-		} 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_queue_completed (cb_data,
-			                         NM_CONNECTIVITY_FULL,
-			                         "no content, as expected",
-			                         NULL);
-		} 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_queue_completed (cb_data,
-			                         NM_CONNECTIVITY_PORTAL,
-			                         "unexpected short response",
-			                         NULL);
+			                         g_strdup_printf ("check failed: (%d) %s",
+			                                          msg->data.result,
+			                                          curl_easy_strerror (msg->data.result)));
+			continue;
+		}
+
+		response = _con_config_get_response (cb_data->concheck.con_config);
+
+		if (   response[0] == '\0'
+		    && (curl_easy_getinfo (msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code) == CURLE_OK)) {
+
+			if (response_code == 204) {
+				/* We expected an empty response, and we got a 204 response code (no content).
+				 * We may or may not have received any content (we would ignore it).
+				 * Anyway, the response_code 204 means we are good. */
+				cb_data_queue_completed (cb_data,
+				                         NM_CONNECTIVITY_FULL,
+				                         "no content, as expected",
+				                         NULL);
+				continue;
+			}
+
+			if (   response_code == 200
+			    && cb_data->concheck.response_good_cnt == 0) {
+				/* we expected no response, and indeed we got an empty reply (with status code 200) */
+				cb_data_queue_completed (cb_data,
+				                         NM_CONNECTIVITY_FULL,
+				                         "empty response, as expected",
+				                         NULL);
+				continue;
+			}
 		}
+
+		/* 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_queue_completed (cb_data,
+		                         NM_CONNECTIVITY_PORTAL,
+		                         "unexpected short response",
+		                         NULL);
 	}
 
 	/* if we return a failure, we don't know what went wrong. It's likely serious, because
@@ -346,29 +417,27 @@ _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 static gboolean
 _con_curl_timeout_cb (gpointer user_data)
 {
-	gs_unref_object NMConnectivity *self = g_object_ref (NM_CONNECTIVITY (user_data));
-	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	NMConnectivityCheckHandle *cb_data = user_data;
 
-	priv->concheck.curl_timer = 0;
-	_con_curl_check_connectivity (priv->concheck.curl_mhandle, CURL_SOCKET_TIMEOUT, 0);
-	_complete_queued (self);
+	cb_data->concheck.curl_timer = 0;
+	_con_curl_check_connectivity (cb_data->concheck.curl_mhandle, CURL_SOCKET_TIMEOUT, 0);
+	_complete_queued (cb_data->self);
 	return G_SOURCE_REMOVE;
 }
 
 static int
 multi_timer_cb (CURLM *multi, long timeout_ms, void *userdata)
 {
-	NMConnectivity *self = NM_CONNECTIVITY (userdata);
-	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	NMConnectivityCheckHandle *cb_data = userdata;
 
-	nm_clear_g_source (&priv->concheck.curl_timer);
+	nm_clear_g_source (&cb_data->concheck.curl_timer);
 	if (timeout_ms != -1)
-		priv->concheck.curl_timer = g_timeout_add (timeout_ms, _con_curl_timeout_cb, self);
+		cb_data->concheck.curl_timer = g_timeout_add (timeout_ms, _con_curl_timeout_cb, cb_data);
 	return 0;
 }
 
 typedef struct {
-	NMConnectivity *self;
+	NMConnectivityCheckHandle *cb_data;
 	GIOChannel *ch;
 
 	/* this is a very simplistic weak-pointer. If ConCurlSockData gets
@@ -385,8 +454,7 @@ static gboolean
 _con_curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer user_data)
 {
 	ConCurlSockData *fdp = user_data;
-	gs_unref_object NMConnectivity *self = g_object_ref (fdp->self);
-	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	NMConnectivityCheckHandle *cb_data = fdp->cb_data;
 	int fd = g_io_channel_unix_get_fd (ch);
 	int action = 0;
 	gboolean fdp_destroyed = FALSE;
@@ -402,7 +470,7 @@ _con_curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer user_
 	nm_assert (!fdp->destroy_notify);
 	fdp->destroy_notify = &fdp_destroyed;
 
-	success = _con_curl_check_connectivity (priv->concheck.curl_mhandle, fd, action);
+	success = _con_curl_check_connectivity (cb_data->concheck.curl_mhandle, fd, action);
 
 	if (fdp_destroyed) {
 		/* hups. fdp got invalidated during _con_curl_check_connectivity(). That's fine,
@@ -414,7 +482,7 @@ _con_curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer user_
 			fdp->ev = 0;
 	}
 
-	_complete_queued (self);
+	_complete_queued (cb_data->self);
 
 	return success ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
 }
@@ -422,8 +490,7 @@ _con_curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer user_
 static int
 multi_socket_cb (CURL *e_handle, curl_socket_t fd, int what, void *userdata, void *socketp)
 {
-	NMConnectivity *self = NM_CONNECTIVITY (userdata);
-	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	NMConnectivityCheckHandle *cb_data = userdata;
 	ConCurlSockData *fdp = socketp;
 	GIOCondition condition = 0;
 
@@ -433,7 +500,7 @@ multi_socket_cb (CURL *e_handle, curl_socket_t fd, int what, void *userdata, voi
 		if (fdp) {
 			if (fdp->destroy_notify)
 				*fdp->destroy_notify = TRUE;
-			curl_multi_assign (priv->concheck.curl_mhandle, fd, NULL);
+			curl_multi_assign (cb_data->concheck.curl_mhandle, fd, NULL);
 			nm_clear_g_source (&fdp->ev);
 			g_io_channel_unref (fdp->ch);
 			g_slice_free (ConCurlSockData, fdp);
@@ -441,9 +508,9 @@ multi_socket_cb (CURL *e_handle, curl_socket_t fd, int what, void *userdata, voi
 	} else {
 		if (!fdp) {
 			fdp = g_slice_new0 (ConCurlSockData);
-			fdp->self = self;
+			fdp->cb_data = cb_data;
 			fdp->ch = g_io_channel_unix_new (fd);
-			curl_multi_assign (priv->concheck.curl_mhandle, fd, fdp);
+			curl_multi_assign (cb_data->concheck.curl_mhandle, fd, fdp);
 		} else
 			nm_clear_g_source (&fdp->ev);
 
@@ -489,6 +556,8 @@ easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
 {
 	NMConnectivityCheckHandle *cb_data = userdata;
 	size_t len = size * nmemb;
+	size_t response_len;
+	size_t check_len;
 	const char *response;
 
 	if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) {
@@ -496,26 +565,68 @@ easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
 		return 0;
 	}
 
-	if (!cb_data->concheck.recv_msg)
-		cb_data->concheck.recv_msg = g_string_sized_new (len + 10);
+	if (len == 0) {
+		/* no data. That can happen, it's fine. */
+		return len;
+	}
 
-	g_string_append_len (cb_data->concheck.recv_msg, buffer, len);
+	response = _con_config_get_response (cb_data->concheck.con_config);;
 
-	response = _check_handle_get_response (cb_data);;
-	if (   response
-	    && cb_data->concheck.recv_msg->len >= strlen (response)) {
-		/* We already have enough data -- check response */
-		if (g_str_has_prefix (cb_data->concheck.recv_msg->str, response)) {
-			cb_data_queue_completed (cb_data,
-			                         NM_CONNECTIVITY_FULL,
-			                         "expected response",
-			                         NULL);
-		} else {
+	if (response[0] == '\0') {
+		/* no response expected. We are however graceful and accept any
+		 * extra response that we might receive. We determine the empty
+		 * response based on the status code 204.
+		 *
+		 * Continue receiving... */
+		cb_data->concheck.response_good_cnt += len;
+
+		if (cb_data->concheck.response_good_cnt > (gsize) (100 * 1024)) {
+			/* we expect an empty response. We accept either
+			 * 1) status code 204 and any response
+			 * 2) status code 200 and an empty response.
+			 *
+			 * Here, we want to continue receiving data, to see whether we have
+			 * case 1). Arguably, the server shouldn't send us 204 with a non-empty
+			 * response, but we accept that also with a non-empty response, so
+			 * keep receiving.
+			 *
+			 * However, if we get an excessive amount of data, we put a stop on it
+			 * and fail. */
 			cb_data_queue_completed (cb_data,
 			                         NM_CONNECTIVITY_PORTAL,
-			                         "unexpected response",
+			                         "unexpected non-empty response",
 			                         NULL);
+			return 0;
 		}
+
+		return len;
+	}
+
+	nm_assert (cb_data->concheck.response_good_cnt < strlen (response));
+
+	response_len = strlen (response);
+
+	check_len = NM_MIN (len,
+	                    response_len - cb_data->concheck.response_good_cnt);
+
+	if (strncmp (&response[cb_data->concheck.response_good_cnt],
+	             buffer,
+	             check_len) != 0) {
+		cb_data_queue_completed (cb_data,
+		                         NM_CONNECTIVITY_PORTAL,
+		                         "unexpected response",
+		                         NULL);
+		return 0;
+	}
+
+	cb_data->concheck.response_good_cnt += len;
+
+	if (cb_data->concheck.response_good_cnt >= response_len) {
+		/* We already have enough data, and it matched. */
+		cb_data_queue_completed (cb_data,
+		                         NM_CONNECTIVITY_FULL,
+		                         "expected response",
+		                         NULL);
 		return 0;
 	}
 
@@ -551,66 +662,235 @@ _idle_cb (gpointer user_data)
 		g_set_error (&error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT,
 		             "no interface specified for connectivity check");
 		cb_data_complete (cb_data, NM_CONNECTIVITY_ERROR, "missing interface");
+	} else if (cb_data->fail_reason_no_dbus_connection) {
+		gs_free_error GError *error = NULL;
+
+		g_set_error (&error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT,
+		             "no D-Bus connection");
+		cb_data_complete (cb_data, NM_CONNECTIVITY_ERROR, "no D-Bus connection");
 	} else
 		cb_data_complete (cb_data, NM_CONNECTIVITY_FAKE, "fake result");
 	return G_SOURCE_REMOVE;
 }
 
+static void
+do_curl_request (NMConnectivityCheckHandle *cb_data)
+{
+	CURLM *mhandle;
+	CURL *ehandle;
+	long resolve;
+
+	mhandle = curl_multi_init ();
+	if (!mhandle) {
+		cb_data_complete (cb_data, NM_CONNECTIVITY_ERROR, "curl error");
+		return;
+	}
+
+	ehandle = curl_easy_init ();
+	if (!ehandle) {
+		curl_multi_cleanup (mhandle);
+		cb_data_complete (cb_data, NM_CONNECTIVITY_ERROR, "curl error");
+		return;
+	}
+
+	cb_data->concheck.curl_mhandle = mhandle;
+	cb_data->concheck.curl_ehandle = ehandle;
+	cb_data->concheck.request_headers = curl_slist_append (NULL, "Connection: close");
+	cb_data->timeout_id = g_timeout_add_seconds (20, _timeout_cb, cb_data);
+
+	curl_multi_setopt (mhandle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
+	curl_multi_setopt (mhandle, CURLMOPT_SOCKETDATA, cb_data);
+	curl_multi_setopt (mhandle, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
+	curl_multi_setopt (mhandle, CURLMOPT_TIMERDATA, cb_data);
+	curl_multi_setopt (mhandle, CURLOPT_VERBOSE, 1);
+
+	switch (cb_data->addr_family) {
+	case AF_INET:
+		resolve = CURL_IPRESOLVE_V4;
+		break;
+	case AF_INET6:
+		resolve = CURL_IPRESOLVE_V6;
+		break;
+	case AF_UNSPEC:
+		resolve = CURL_IPRESOLVE_WHATEVER;
+		break;
+	default:
+		resolve = CURL_IPRESOLVE_WHATEVER;
+		g_warn_if_reached ();
+	}
+
+	curl_easy_setopt (ehandle, CURLOPT_URL, cb_data->concheck.con_config->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_easy_setopt (ehandle, CURLOPT_RESOLVE, cb_data->concheck.hosts);
+	curl_easy_setopt (ehandle, CURLOPT_IPRESOLVE, resolve);
+
+	curl_multi_add_handle (mhandle, ehandle);
+}
+
+static void
+resolve_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+	NMConnectivityCheckHandle *cb_data;
+	gs_unref_variant GVariant *result = NULL;
+	gs_unref_variant GVariant *addresses = NULL;
+	gsize no_addresses;
+	int ifindex;
+	int addr_family;
+	gsize len = 0;
+	gsize i;
+	gs_free_error GError *error = NULL;
+
+	result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), res, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	cb_data = user_data;
+
+	g_clear_object (&cb_data->concheck.resolve_cancellable);
+
+	if (!result) {
+		/* Never mind. Just let do curl do its own resolving. */
+		_LOG2D ("can't resolve a name via systemd-resolved: %s", error->message);
+		do_curl_request (cb_data);
+		return;
+	}
+
+	addresses = g_variant_get_child_value (result, 0);
+	no_addresses = g_variant_n_children (addresses);
+
+	for (i = 0; i < no_addresses; i++) {
+		gs_unref_variant GVariant *address = NULL;
+		char str_addr[NM_UTILS_INET_ADDRSTRLEN];
+		gs_free char *host_entry = NULL;
+		const guchar *address_buf;
+
+		g_variant_get_child (addresses, i, "(ii@ay)", &ifindex, &addr_family, &address);
+
+		if (   cb_data->addr_family != AF_UNSPEC
+		    && cb_data->addr_family != addr_family)
+			continue;
+
+		address_buf = g_variant_get_fixed_array (address, &len, 1);
+		if (   (addr_family == AF_INET  && len != sizeof (struct in_addr))
+		    || (addr_family == AF_INET6 && len != sizeof (struct in6_addr)))
+			continue;
+
+		host_entry = g_strdup_printf ("%s:%s:%s",
+		                              cb_data->concheck.con_config->host,
+		                              cb_data->concheck.con_config->port ?: "80",
+		                              nm_utils_inet_ntop (addr_family, address_buf, str_addr));
+		cb_data->concheck.hosts = curl_slist_append (cb_data->concheck.hosts, host_entry);
+		_LOG2T ("adding '%s' to curl resolve list", host_entry);
+	}
+
+	do_curl_request (cb_data);
+}
+
+#define SD_RESOLVED_DNS ((guint64) (1LL << 0))
+
 NMConnectivityCheckHandle *
 nm_connectivity_check_start (NMConnectivity *self,
+                             int addr_family,
+                             int ifindex,
                              const char *iface,
                              NMConnectivityCheckCallback callback,
                              gpointer user_data)
 {
 	NMConnectivityPrivate *priv;
 	NMConnectivityCheckHandle *cb_data;
+	static guint64 request_counter = 0;
 
 	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);
 
 	priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
 	cb_data = g_slice_new0 (NMConnectivityCheckHandle);
 	cb_data->self = self;
+	cb_data->request_counter = ++request_counter;
 	c_list_link_tail (&priv->handles_lst_head, &cb_data->handles_lst);
 	cb_data->callback = callback;
 	cb_data->user_data = user_data;
 	cb_data->completed_state = NM_CONNECTIVITY_UNKNOWN;
+	cb_data->addr_family = addr_family;
+	cb_data->concheck.con_config = _con_config_ref (priv->con_config);
 
 	if (iface)
 		cb_data->ifspec = g_strdup_printf ("if!%s", iface);
 
 #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 (   iface
+	    && ifindex > 0
+	    && priv->enabled
+	    && priv->uri_valid) {
+		gboolean has_systemd_resolved;
+
+		cb_data->concheck.ch_ifindex = ifindex;
+
+		/* note that we pick up support for systemd-resolved right away when we need it.
+		 * We don't need to remember the setting, because we can (cheaply) check anew
+		 * on each request.
+		 *
+		 * Yes, this makes NMConnectivity singleton dependent on NMDnsManager singleton.
+		 * Well, not really: it makes connectivity-check-start dependent on NMDnsManager
+		 * which merely means, not to start a connectivity check, late during shutdown. */
+		has_systemd_resolved = nm_dns_manager_has_systemd_resolved (nm_dns_manager_get ());
+
+		if (has_systemd_resolved) {
+			GDBusConnection *dbus_connection;
+
+			dbus_connection = nm_dbus_manager_get_dbus_connection (nm_dbus_manager_get ());
+			if (!dbus_connection) {
+				/* we have no D-Bus connection? That might happen in configure and quit mode.
+				 *
+				 * Anyway, something is very odd, just fail connectivity check. */
+				_LOG2D ("start fake request (fail due to no D-Bus connection)");
+				cb_data->fail_reason_no_dbus_connection = TRUE;
+				cb_data->timeout_id = g_idle_add (_idle_cb, cb_data);
+				return cb_data;
+			}
+
+			cb_data->concheck.resolve_cancellable = g_cancellable_new ();
+
+			g_dbus_connection_call (nm_dbus_manager_get_dbus_connection (nm_dbus_manager_get ()),
+			                        "org.freedesktop.resolve1",
+			                        "/org/freedesktop/resolve1",
+			                        "org.freedesktop.resolve1.Manager",
+			                        "ResolveHostname",
+			                        g_variant_new ("(isit)",
+			                                       (gint32) cb_data->concheck.ch_ifindex,
+			                                       cb_data->concheck.con_config->host,
+			                                       (gint32) cb_data->addr_family,
+			                                       SD_RESOLVED_DNS),
+			                        G_VARIANT_TYPE ("(a(iiay)st)"),
+			                        G_DBUS_CALL_FLAGS_NONE,
+			                        -1,
+			                        cb_data->concheck.resolve_cancellable,
+			                        resolve_cb,
+			                        cb_data);
+			_LOG2D ("start request to '%s' (try resolving '%s' using systemd-resolved)",
+			        cb_data->concheck.con_config->uri,
+			        cb_data->concheck.con_config->host);
+		} else {
+			_LOG2D ("start request to '%s' (systemd-resolved not available)",
+			        cb_data->concheck.con_config->uri);
+			do_curl_request (cb_data);
 		}
+
+		return cb_data;
 	}
 #endif
 
 	_LOG2D ("start fake request");
 	cb_data->timeout_id = g_idle_add (_idle_cb, cb_data);
+
 	return cb_data;
 }
 
@@ -646,42 +926,126 @@ nm_connectivity_get_interval (NMConnectivity *self)
 	       : 0;
 }
 
+static gboolean
+host_and_port_from_uri (const char *uri, char **host, char **port)
+{
+	const char *p = uri;
+	const char *host_begin = NULL;
+	size_t host_len = 0;
+	const char *port_begin = NULL;
+	size_t port_len = 0;
+
+	/* scheme */
+	while (*p != ':' && *p != '/') {
+		if (!*p++)
+			return FALSE;
+	}
+
+	/* :// */
+	if (*p++ != ':')
+		return FALSE;
+	if (*p++ != '/')
+		return FALSE;
+	if (*p++ != '/')
+		return FALSE;
+	/* host */
+	if (*p == '[')
+		return FALSE;
+	host_begin = p;
+	while (*p && *p != ':' && *p != '/') {
+		host_len++;
+		p++;
+	}
+	if (host_len == 0)
+		return FALSE;
+	*host = g_strndup (host_begin, host_len);
+
+	/* port */
+	if (*p++ == ':') {
+		port_begin = p;
+		while (*p && *p != '/') {
+			port_len++;
+			p++;
+		}
+		if (port_len)
+			*port = g_strndup (port_begin, port_len);
+	}
+
+	return TRUE;
+}
+
 static void
 update_config (NMConnectivity *self, NMConfigData *config_data)
 {
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-	const char *uri, *response;
 	guint interval;
 	gboolean enabled;
 	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 doesn't use a scheme that is allowed for connectivity check.", uri);
-			uri = NULL;
+	const char *cur_uri = priv->con_config ? priv->con_config->uri : NULL;
+	const char *cur_response = priv->con_config ? priv->con_config->response : NULL;
+	const char *new_response;
+	const char *new_uri;
+	gboolean new_uri_valid = priv->uri_valid;
+	gboolean new_host_port = FALSE;
+	gs_free char *new_host = NULL;
+	gs_free char *new_port = NULL;
+
+	new_uri = nm_config_data_get_connectivity_uri (config_data);
+	if (!nm_streq0 (new_uri, cur_uri)) {
+
+		new_uri_valid = (new_uri && *new_uri);
+		if (new_uri_valid) {
+			gs_free char *scheme = g_uri_parse_scheme (new_uri);
+			gboolean is_https = FALSE;
+
+			if (!scheme) {
+				_LOGE ("invalid URI '%s' for connectivity check.", new_uri);
+				new_uri_valid = FALSE;
+			} else if (g_ascii_strcasecmp (scheme, "https") == 0) {
+				_LOGW ("use of HTTPS for connectivity checking is not reliable and is discouraged (URI: %s)", new_uri);
+				is_https = TRUE;
+			} else if (g_ascii_strcasecmp (scheme, "http") != 0) {
+				_LOGE ("scheme of '%s' uri doesn't use a scheme that is allowed for connectivity check.", new_uri);
+				new_uri_valid = FALSE;
+			}
+			if (new_uri_valid) {
+				new_host_port = TRUE;
+				if (!host_and_port_from_uri (new_uri, &new_host, &new_port)) {
+					_LOGE ("cannot parse host and port from '%s'", new_uri);
+					new_uri_valid = FALSE;
+				} else if (!new_port && is_https)
+					new_port = g_strdup ("443");
+			}
 		}
 
-		if (scheme)
-			g_free (scheme);
+		if (   new_uri_valid
+		    || priv->uri_valid != new_uri_valid)
+			changed = TRUE;
 	}
-	if (changed) {
-		g_free (priv->uri);
-		priv->uri = g_strdup (uri);
+
+	new_response = nm_config_data_get_connectivity_response (config_data);
+	if (!nm_streq0 (new_response, cur_response))
+		changed = TRUE;
+
+	if (   !priv->con_config
+	    || !nm_streq0 (new_uri, priv->con_config->uri)
+	    || !nm_streq0 (new_response, priv->con_config->response)) {
+		if (!new_host_port) {
+			new_host = priv->con_config ? g_strdup (priv->con_config->host) : NULL;
+			new_port = priv->con_config ? g_strdup (priv->con_config->port) : NULL;
+		}
+		_con_config_unref (priv->con_config);
+		priv->con_config = g_slice_new (ConConfig);
+		*priv->con_config = (ConConfig) {
+			.ref_count = 1,
+			.uri       = g_strdup (new_uri),
+			.response  = g_strdup (new_response),
+			.host      = g_steal_pointer (&new_host),
+			.port      = g_steal_pointer (&new_port),
+		};
 	}
+	priv->uri_valid = new_uri_valid;
 
-	/* Set the interval. */
 	interval = nm_config_data_get_connectivity_interval (config_data);
 	interval = MIN (interval, (7 * 24 * 3600));
 	if (priv->interval != interval) {
@@ -691,11 +1055,8 @@ update_config (NMConnectivity *self, NMConfigData *config_data)
 
 	enabled = FALSE;
 #if WITH_CONCHECK
-	/* connectivity checking also requires a valid URI, interval and
-	 * curl_mhandle */
-	if (   priv->uri
-	    && priv->interval
-	    && priv->concheck.curl_mhandle)
+	if (   priv->uri_valid
+	    && priv->interval)
 		enabled = nm_config_data_get_connectivity_enabled (config_data);
 #endif
 
@@ -704,16 +1065,6 @@ update_config (NMConnectivity *self, NMConfigData *config_data)
 		changed = TRUE;
 	}
 
-	/* Set the response. */
-	response = nm_config_data_get_connectivity_response (config_data);
-	if (!nm_streq0 (response, priv->response)) {
-		/* 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)
 		g_signal_emit (self, signals[CONFIG_CHANGED], 0);
 }
@@ -734,6 +1085,9 @@ static void
 nm_connectivity_init (NMConnectivity *self)
 {
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+#if WITH_CONCHECK
+	CURLcode ret;
+#endif
 
 	c_list_init (&priv->handles_lst_head);
 	c_list_init (&priv->completed_handles_lst_head);
@@ -745,17 +1099,10 @@ nm_connectivity_init (NMConnectivity *self)
 	                  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);
+	ret = curl_global_init (CURL_GLOBAL_ALL);
+	if (ret != CURLE_OK) {
+		_LOGE ("unable to init cURL, connectivity check will not work: (%d) %s",
+		       ret, curl_easy_strerror (ret));
 	}
 #endif
 
@@ -776,13 +1123,9 @@ dispose (GObject *object)
 	                                      handles_lst)))
 		cb_data_complete (cb_data, NM_CONNECTIVITY_DISPOSING, "shutting down");
 
-	g_clear_pointer (&priv->uri, g_free);
-	g_clear_pointer (&priv->response, g_free);
+	nm_clear_pointer (&priv->con_config, _con_config_unref);
 
 #if WITH_CONCHECK
-	nm_clear_g_source (&priv->concheck.curl_timer);
-
-	curl_multi_cleanup (priv->concheck.curl_mhandle);
 	curl_global_cleanup ();
 #endif