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.c299
1 files changed, 183 insertions, 116 deletions
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index 85bd70ae..81ac8f9d 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -26,25 +26,43 @@
 #include <libsoup/soup.h>
 #endif
 
+#include "nm-glib-compat.h"
+
 #include "nm-connectivity.h"
-#include "nm-logging.h"
 #include "nm-config.h"
+#include "nm-logging.h"
 
 G_DEFINE_TYPE (NMConnectivity, nm_connectivity, G_TYPE_OBJECT)
 
 #define NM_CONNECTIVITY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_CONNECTIVITY, NMConnectivityPrivate))
 
 
-#define DEFAULT_RESPONSE "NetworkManager is online" /* NOT LOCALIZED */
+#define _LOG_DEFAULT_DOMAIN  LOGD_CONCHECK
+
+#define _LOG(level, domain, ...) \
+    G_STMT_START { \
+        nm_log ((level), (domain), \
+                "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                "connectivity: " \
+                _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    } G_STMT_END
+
+#define _LOGT(...)      _LOG (LOGL_TRACE, _LOG_DEFAULT_DOMAIN, __VA_ARGS__)
+#define _LOGD(...)      _LOG (LOGL_DEBUG, _LOG_DEFAULT_DOMAIN, __VA_ARGS__)
+#define _LOGI(...)      _LOG (LOGL_INFO,  _LOG_DEFAULT_DOMAIN, __VA_ARGS__)
+#define _LOGW(...)      _LOG (LOGL_WARN,  _LOG_DEFAULT_DOMAIN, __VA_ARGS__)
+#define _LOGE(...)      _LOG (LOGL_ERR,   _LOG_DEFAULT_DOMAIN, __VA_ARGS__)
+
 
 typedef struct {
 	char *uri;
 	char *response;
 	guint interval;
+	gboolean online; /* whether periodic connectivity checking is enabled. */
 
 #if WITH_CONCHECK
 	SoupSession *soup_session;
-	guint pending_checks;
+	gboolean initial_check_obsoleted;
 	guint check_id;
 #endif
 
@@ -69,8 +87,8 @@ nm_connectivity_get_state (NMConnectivity *connectivity)
 	return NM_CONNECTIVITY_GET_PRIVATE (connectivity)->state;
 }
 
-static const char *
-state_name (NMConnectivityState state)
+const char *
+nm_connectivity_state_to_string (NMConnectivityState state)
 {
 	switch (state) {
 	case NM_CONNECTIVITY_UNKNOWN:
@@ -84,7 +102,7 @@ state_name (NMConnectivityState state)
 	case NM_CONNECTIVITY_FULL:
 		return "FULL";
 	default:
-		return "???";
+		g_return_val_if_reached ("???");
 	}
 }
 
@@ -94,31 +112,41 @@ update_state (NMConnectivity *self, NMConnectivityState state)
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
 	if (priv->state != state) {
-		nm_log_dbg (LOGD_CONCHECK, "Connectivity state changed from %s to %s",
-		            state_name (priv->state), state_name (state));
+		_LOGD ("state changed from %s to %s",
+		       nm_connectivity_state_to_string (priv->state),
+		       nm_connectivity_state_to_string (state));
 		priv->state = state;
 		g_object_notify (G_OBJECT (self), NM_CONNECTIVITY_STATE);
 	}
 }
 
 #if WITH_CONCHECK
+typedef struct {
+	GSimpleAsyncResult *simple;
+	char *uri;
+	char *response;
+	guint check_id_when_scheduled;
+} ConCheckCbData;
+
 static void
 nm_connectivity_check_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
 {
-	GSimpleAsyncResult *simple = user_data;
 	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);
-	priv->pending_checks--;
 
 	if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
-		nm_log_info (LOGD_CONCHECK, "Connectivity check for uri '%s' failed with '%s'.",
-		             priv->uri, msg->reason_phrase);
+		_LOGI ("check for uri '%s' failed with '%s'", uri, msg->reason_phrase);
 		new_state = NM_CONNECTIVITY_LIMITED;
 		goto done;
 	}
@@ -126,33 +154,48 @@ nm_connectivity_check_cb (SoupSession *session, SoupMessage *msg, gpointer user_
 	/* 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) {
-		nm_log_dbg (LOGD_CONCHECK, "Connectivity check for uri '%s' with Status header successful.", priv->uri);
+		_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, priv->response))) {
-			nm_log_dbg (LOGD_CONCHECK, "Connectivity check for uri '%s' successful.",
-			            priv->uri);
+		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;
 		} else {
-			nm_log_info (LOGD_CONCHECK, "Connectivity check for uri '%s' did not match expected response '%s'; assuming captive portal.",
-			             priv->uri, priv->response);
+			_LOGI ("check for uri '%s' did not match expected response '%s'; assuming captive portal.",
+			       uri, response);
 			new_state = NM_CONNECTIVITY_PORTAL;
 		}
 	} else {
-		nm_log_info (LOGD_CONCHECK, "Connectivity check for uri '%s' returned status '%d %s'; assuming captive portal.",
-		             priv->uri, msg->status_code, msg->reason_phrase);
+		_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:
-	update_state (self, new_state);
+	/* 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);
+	}
 
 	g_simple_async_result_set_op_res_gssize (simple, new_state);
 	g_simple_async_result_complete (simple);
 	g_object_unref (simple);
+
+	g_free (cb_data->uri);
+	g_free (cb_data->response);
+	g_slice_free (ConCheckCbData, cb_data);
 }
 
+#define IS_PERIODIC_CHECK(callback)  (callback == run_check_complete)
+
 static void
 run_check_complete (GObject      *object,
                     GAsyncResult *result,
@@ -163,7 +206,7 @@ run_check_complete (GObject      *object,
 
 	nm_connectivity_check_finish (self, result, &error);
 	if (error) {
-		nm_log_err (LOGD_CONCHECK, "Connectivity check failed: %s", error->message);
+		_LOGE ("check failed: %s", error->message);
 		g_error_free (error);
 	}
 }
@@ -184,39 +227,54 @@ idle_start_periodic_checks (gpointer user_data)
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
 	priv->check_id = g_timeout_add_seconds (priv->interval, run_check, self);
-	if (!priv->pending_checks)
+	if (!priv->initial_check_obsoleted)
 		run_check (self);
 
 	return FALSE;
 }
 #endif
 
-void
-nm_connectivity_set_online (NMConnectivity *self,
-                            gboolean        online)
+static void
+_reschedule_periodic_checks (NMConnectivity *self, gboolean force_reschedule)
 {
-#if WITH_CONCHECK
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
-#endif
-
-	nm_log_dbg (LOGD_CONCHECK, "nm_connectivity_set_online(%s)", online ? "TRUE" : "FALSE");
 
 #if WITH_CONCHECK
-	if (online && priv->uri && priv->interval) {
-		if (!priv->check_id)
+	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);
-
-		return;
-	} else if (priv->check_id) {
-		g_source_remove (priv->check_id);
-		priv->check_id = 0;
+			priv->initial_check_obsoleted = FALSE;
+		}
+	} else {
+		if (priv->check_id) {
+			g_source_remove (priv->check_id);
+			priv->check_id = 0;
+		}
 	}
+	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, online ? NM_CONNECTIVITY_FULL : NM_CONNECTIVITY_NONE);
+	update_state (self, priv->online ? NM_CONNECTIVITY_FULL : NM_CONNECTIVITY_NONE);
+}
+
+void
+nm_connectivity_set_online (NMConnectivity *self,
+                            gboolean        online)
+{
+	NMConnectivityPrivate *priv= NM_CONNECTIVITY_GET_PRIVATE (self);
+
+	online = !!online;
+	if (priv->online != online) {
+		_LOGD ("set %s", online ? "online" : "offline");
+		priv->online = online;
+		_reschedule_periodic_checks (self, FALSE);
+	}
 }
 
 void
@@ -225,38 +283,44 @@ nm_connectivity_check_async (NMConnectivity      *self,
                              gpointer             user_data)
 {
 	NMConnectivityPrivate *priv;
-#if WITH_CONCHECK
-	SoupMessage *msg;
-#endif
 	GSimpleAsyncResult *simple;
 
 	g_return_if_fail (NM_IS_CONNECTIVITY (self));
 	priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
-#if WITH_CONCHECK
-	if (callback == run_check_complete)
-		nm_log_dbg (LOGD_CONCHECK, "Periodic connectivity check started with uri '%s'.", priv->uri);
-	else
-#endif
-		nm_log_dbg (LOGD_CONCHECK, "Connectivity check started with uri '%s'.", priv->uri);
-
 	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);
+
 		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->simple = simple;
+		cb_data->uri = g_strdup (priv->uri);
+		cb_data->response = g_strdup (priv->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;
+
 		soup_session_queue_message (priv->soup_session,
 		                            msg,
 		                            nm_connectivity_check_cb,
-		                            simple);
-		priv->pending_checks++;
+		                            cb_data);
+		priv->initial_check_obsoleted = TRUE;
 
+		_LOGD ("check: send %srequest to '%s'", IS_PERIODIC_CHECK (callback) ? "periodic " : "", priv->uri);
 		return;
+	} else {
+		g_warn_if_fail (!IS_PERIODIC_CHECK (callback));
+		_LOGD ("check: faking request. Connectivity check disabled");
 	}
+#else
+	_LOGD ("check: faking request. Compiled without connectivity-check support");
 #endif
 
 	g_simple_async_result_set_op_res_gssize (simple, priv->state);
@@ -279,38 +343,18 @@ nm_connectivity_check_finish (NMConnectivity  *self,
 	return (NMConnectivityState) g_simple_async_result_get_op_res_gssize (simple);
 }
 
+/**************************************************************************/
 
 NMConnectivity *
-nm_connectivity_new (void)
+nm_connectivity_new (const char *uri,
+                     guint interval,
+                     const char *response)
 {
-	NMConnectivity *self;
-	NMConfig *config;
-	const char *check_response;
-
-	config = nm_config_get ();
-	check_response = nm_config_get_connectivity_response (config);
-
-	self = g_object_new (NM_TYPE_CONNECTIVITY,
-	                     NM_CONNECTIVITY_URI, nm_config_get_connectivity_uri (config),
-	                     NM_CONNECTIVITY_INTERVAL, nm_config_get_connectivity_interval (config),
-	                     NM_CONNECTIVITY_RESPONSE, check_response ? check_response : DEFAULT_RESPONSE,
+	return g_object_new (NM_TYPE_CONNECTIVITY,
+	                     NM_CONNECTIVITY_URI, uri,
+	                     NM_CONNECTIVITY_INTERVAL, interval,
+	                     NM_CONNECTIVITY_RESPONSE, response,
 	                     NULL);
-	g_return_val_if_fail (self != NULL, NULL);
-	update_state (self, NM_CONNECTIVITY_NONE);
-
-	return self;
-}
-
-static char *
-get_non_empty_string_value (const GValue *val)
-{
-	const char *s;
-
-	s = g_value_get_string (val);
-	if (s && s[0])
-		return g_strdup (s);
-	else
-		return NULL;
 }
 
 static void
@@ -319,32 +363,48 @@ set_property (GObject *object, guint property_id,
 {
 	NMConnectivity *self = NM_CONNECTIVITY (object);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	const char *uri, *response;
+	guint interval;
 
 	switch (property_id) {
 	case PROP_URI:
-		g_free (priv->uri);
-		priv->uri = get_non_empty_string_value (value);
-
+		uri = g_value_get_string (value);
+		if (uri && !*uri)
+			uri = NULL;
 #if WITH_CONCHECK
-		if (priv->uri) {
-			SoupURI *uri = soup_uri_new (priv->uri);
+		if (uri) {
+			SoupURI *soup_uri = soup_uri_new (uri);
 
-			if (!uri || !SOUP_URI_VALID_FOR_HTTP (uri)) {
-				nm_log_err (LOGD_CONCHECK, "Invalid uri '%s' for connectivity check.", priv->uri);
-				g_free (priv->uri);
-				priv->uri = NULL;
+			if (!soup_uri || !SOUP_URI_VALID_FOR_HTTP (soup_uri)) {
+				_LOGE ("invalid uri '%s' for connectivity check.", uri);
+				uri = NULL;
 			}
-			if (uri)
-				soup_uri_free (uri);
+			if (soup_uri)
+				soup_uri_free (soup_uri);
 		}
 #endif
+		if (g_strcmp0 (uri, priv->uri) != 0) {
+			g_free (priv->uri);
+			priv->uri = g_strdup (uri);
+			_reschedule_periodic_checks (self, TRUE);
+		}
 		break;
 	case PROP_INTERVAL:
-		priv->interval = g_value_get_uint (value);
+		interval = g_value_get_uint (value);
+		if (priv->interval != interval) {
+			priv->interval = interval;
+			_reschedule_periodic_checks (self, TRUE);
+		}
 		break;
 	case PROP_RESPONSE:
-		g_free (priv->response);
-		priv->response = get_non_empty_string_value (value);
+		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);
@@ -367,7 +427,10 @@ get_property (GObject *object, guint property_id,
 		g_value_set_uint (value, priv->interval);
 		break;
 	case PROP_RESPONSE:
-		g_value_set_string (value, priv->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);
@@ -382,11 +445,12 @@ get_property (GObject *object, guint property_id,
 static void
 nm_connectivity_init (NMConnectivity *self)
 {
-#if WITH_CONCHECK
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
+#if WITH_CONCHECK
 	priv->soup_session = soup_session_async_new_with_options (SOUP_SESSION_TIMEOUT, 15, NULL);
 #endif
+	priv->state = NM_CONNECTIVITY_NONE;
 }
 
 
@@ -396,8 +460,8 @@ dispose (GObject *object)
 	NMConnectivity *self = NM_CONNECTIVITY (object);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
-	g_free (priv->uri);
-	g_free (priv->response);
+	g_clear_pointer (&priv->uri, g_free);
+	g_clear_pointer (&priv->response, g_free);
 
 #if WITH_CONCHECK
 	if (priv->soup_session) {
@@ -426,31 +490,34 @@ nm_connectivity_class_init (NMConnectivityClass *klass)
 
 	/* properties */
 	g_object_class_install_property
-		(object_class, PROP_URI,
-		 g_param_spec_string (NM_CONNECTIVITY_URI, "", "",
-		                      NULL,
-		                      G_PARAM_READWRITE |
-		                      G_PARAM_STATIC_STRINGS));
+	    (object_class, PROP_URI,
+	     g_param_spec_string (NM_CONNECTIVITY_URI, "", "",
+	                          NULL,
+	                          G_PARAM_READWRITE |
+	                          G_PARAM_CONSTRUCT |
+	                          G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
-		(object_class, PROP_INTERVAL,
-		 g_param_spec_uint (NM_CONNECTIVITY_INTERVAL, "", "",
-		                    0, G_MAXUINT, 300,
-		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
-		                    G_PARAM_STATIC_STRINGS));
+	    (object_class, 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));
 
 	g_object_class_install_property
-		(object_class, PROP_RESPONSE,
-		 g_param_spec_string (NM_CONNECTIVITY_RESPONSE, "", "",
-		                      DEFAULT_RESPONSE,
-		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
-		                      G_PARAM_STATIC_STRINGS));
+	    (object_class, PROP_RESPONSE,
+	     g_param_spec_string (NM_CONNECTIVITY_RESPONSE, "", "",
+	                          NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE,
+	                          G_PARAM_READWRITE |
+	                          G_PARAM_CONSTRUCT |
+	                          G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
-		(object_class, 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));
+	    (object_class, 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));
 }