diff options
Diffstat (limited to 'src/nm-connectivity.c')
| -rw-r--r-- | src/nm-connectivity.c | 299 |
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)); } |