diff options
author | Francesco Giudici <fgiudici@redhat.com> | 2016-03-29 10:59:25 +0200 |
---|---|---|
committer | Francesco Giudici <fgiudici@redhat.com> | 2016-04-04 14:56:32 +0200 |
commit | ec84145b039cf629d428298c1436a8c7ac8e459e (patch) | |
tree | fbe5b907ee4d833b2a0156f40f400e048ce74bd4 | |
parent | 2b394d00925ab2c5615f972aa7c338dd243d936b (diff) | |
download | NetworkManager-ec84145b039cf629d428298c1436a8c7ac8e459e.tar.gz |
connectivity check: isolate library dependant code (libsoup)
-rw-r--r-- | src/nm-connectivity-soup.c | 235 | ||||
-rw-r--r-- | src/nm-connectivity-soup.h | 36 | ||||
-rw-r--r-- | src/nm-connectivity.c | 187 |
3 files changed, 282 insertions, 176 deletions
diff --git a/src/nm-connectivity-soup.c b/src/nm-connectivity-soup.c new file mode 100644 index 0000000000..12513c45c7 --- /dev/null +++ b/src/nm-connectivity-soup.c @@ -0,0 +1,235 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2016 Red Hat, Inc. + * + */ + +/* nm_connectivity_check_cb () + * run_check_complete () + * run_check () + * idle_start_periodic_checks () + */ +static void +nm_connectivity_check_cb (SoupSession *session, SoupMessage *msg, gpointer 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); + + 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; + } + + 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; + } else { + _LOGI ("check for uri '%s' did not match expected response '%s'; assuming captive portal.", + uri, response); + new_state = NM_CONNECTIVITY_PORTAL; + } + } 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->concheck.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, + gpointer user_data) +{ + NMConnectivity *self = NM_CONNECTIVITY (object); + GError *error = NULL; + + nm_connectivity_check_finish (self, result, &error); + if (error) { + _LOGE ("check failed: %s", error->message); + g_error_free (error); + } +} + +static gboolean +run_check (gpointer user_data) +{ + NMConnectivity *self = NM_CONNECTIVITY (user_data); + + nm_connectivity_check_async (self, run_check_complete, NULL); + return TRUE; +} + +static gboolean +idle_start_periodic_checks (gpointer user_data) +{ + NMConnectivity *self = user_data; + NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self); + + priv->concheck.check_id = g_timeout_add_seconds (priv->interval, run_check, self); + if (!priv->concheck.initial_check_obsoleted) + run_check (self); + + return FALSE; +} + +/* SOUP specific + * nm_connectivity_do_check () + */ +static gboolean +nm_connectivity_do_check (char *uri, + char *response, + guint interval, + GAsyncReadyCallback callback, + GSimpleAsyncResult *simple, + NMConnectivityConcheck *concheck) +{ + if (uri && interval) { + SoupMessage *msg; + ConCheckCbData *cb_data = g_slice_new (ConCheckCbData); + + msg = soup_message_new ("GET", 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 (uri); + cb_data->response = g_strdup (response); + + /* For internal calls (periodic), remember the check-id at time of scheduling. */ + cb_data->check_id_when_scheduled = IS_PERIODIC_CHECK (callback) ? concheck->check_id : 0; + + soup_session_queue_message (concheck->soup_session, + msg, + nm_connectivity_check_cb, + cb_data); + concheck->initial_check_obsoleted = TRUE; + + _LOGD ("check: send %srequest to '%s'", IS_PERIODIC_CHECK (callback) ? "periodic " : "", uri); + return TRUE; + } else { + g_warn_if_fail (!IS_PERIODIC_CHECK (callback)); + _LOGD ("check: faking request. Connectivity check disabled"); + return FALSE; + } +} + +static gboolean +nm_connectivity_lib_is_uri_valid (const char *uri, gboolean changed) +{ + SoupURI *soup_uri = soup_uri_new (uri); + gboolean is_valid = TRUE; + + if (!soup_uri || !SOUP_URI_VALID_FOR_HTTP (soup_uri)) { + _LOGE ("invalid uri '%s' for connectivity check.", uri); + is_valid = FALSE; + } else if (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); + return is_valid; +} + +static void +nm_connectivity_lib_init (NMConnectivityConcheck *concheck, guint timeout) +{ + concheck->soup_session = soup_session_async_new_with_options (SOUP_SESSION_TIMEOUT, timeout, NULL); +} + +static void +nm_connectivity_lib_dispose (NMConnectivityConcheck *concheck) +{ + if (concheck->soup_session) { + soup_session_abort (concheck->soup_session); + g_clear_object (&concheck->soup_session); + } + + nm_clear_g_source (&concheck->check_id); +} + +static gboolean +nm_connectivity_lib_reschedule (NMConnectivity *self, + gboolean force_reschedule, + gboolean online, + char *uri, + guint interval, + NMConnectivityConcheck *concheck) +{ + if (online && uri && interval) { + if (force_reschedule || !concheck->check_id) { + if (concheck->check_id) + g_source_remove (concheck->check_id); + concheck->check_id = g_timeout_add (0, idle_start_periodic_checks, self); + concheck->initial_check_obsoleted = FALSE; + } + } else { + nm_clear_g_source (&concheck->check_id); + } + + if (concheck->check_id) + return TRUE; + + return FALSE; +} diff --git a/src/nm-connectivity-soup.h b/src/nm-connectivity-soup.h new file mode 100644 index 0000000000..30e3e746b1 --- /dev/null +++ b/src/nm-connectivity-soup.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2016 Red Hat, Inc. + * + */ + +#include <libsoup/soup.h> + +typedef struct { + SoupSession *soup_session; + gboolean initial_check_obsoleted; + guint check_id; +} NMConnectivityConcheck; + +typedef struct { + GSimpleAsyncResult *simple; + char *uri; + char *response; + guint check_id_when_scheduled; +} ConCheckCbData; + + diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c index 934727fa9e..923cd150cf 100644 --- a/src/nm-connectivity.c +++ b/src/nm-connectivity.c @@ -22,8 +22,9 @@ #include "nm-default.h" #include <string.h> + #if WITH_CONCHECK -#include <libsoup/soup.h> +#include "nm-connectivity-soup.h" #endif #include "nm-connectivity.h" @@ -50,9 +51,7 @@ typedef struct { gboolean online; /* whether periodic connectivity checking is enabled. */ #if WITH_CONCHECK - SoupSession *soup_session; - gboolean initial_check_obsoleted; - guint check_id; + NMConnectivityConcheck concheck; #endif NMConnectivityState state; @@ -67,7 +66,6 @@ enum { LAST_PROP }; - NMConnectivityState nm_connectivity_get_state (NMConnectivity *connectivity) { @@ -100,123 +98,7 @@ update_state (NMConnectivity *self, NMConnectivityState 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) -{ - 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); - - 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; - } - - 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; - } else { - _LOGI ("check for uri '%s' did not match expected response '%s'; assuming captive portal.", - uri, response); - new_state = NM_CONNECTIVITY_PORTAL; - } - } 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); - } - - 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, - gpointer user_data) -{ - NMConnectivity *self = NM_CONNECTIVITY (object); - GError *error = NULL; - - nm_connectivity_check_finish (self, result, &error); - if (error) { - _LOGE ("check failed: %s", error->message); - g_error_free (error); - } -} - -static gboolean -run_check (gpointer user_data) -{ - NMConnectivity *self = NM_CONNECTIVITY (user_data); - - nm_connectivity_check_async (self, run_check_complete, NULL); - return TRUE; -} - -static gboolean -idle_start_periodic_checks (gpointer user_data) -{ - NMConnectivity *self = user_data; - NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self); - - priv->check_id = g_timeout_add_seconds (priv->interval, run_check, self); - if (!priv->initial_check_obsoleted) - run_check (self); - - return FALSE; -} +#include "nm-connectivity-soup.c" #endif static void @@ -225,17 +107,8 @@ _reschedule_periodic_checks (NMConnectivity *self, gboolean force_reschedule) 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; - } - } else { - nm_clear_g_source (&priv->check_id); - } - if (priv->check_id) + if (nm_connectivity_lib_reschedule (self, force_reschedule, priv->online, + priv->uri, priv->interval, &priv->concheck)) return; #endif @@ -274,33 +147,9 @@ nm_connectivity_check_async (NMConnectivity *self, 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, - cb_data); - priv->initial_check_obsoleted = TRUE; - - _LOGD ("check: send %srequest to '%s'", IS_PERIODIC_CHECK (callback) ? "periodic " : "", priv->uri); + if (nm_connectivity_do_check (priv->uri, priv->response, priv->interval, + callback, simple, &(priv->concheck))) 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 @@ -357,17 +206,8 @@ set_property (GObject *object, guint property_id, 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); + if (!nm_connectivity_lib_is_uri_valid(uri, changed)) 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) { @@ -435,7 +275,7 @@ nm_connectivity_init (NMConnectivity *self) NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self); #if WITH_CONCHECK - priv->soup_session = soup_session_async_new_with_options (SOUP_SESSION_TIMEOUT, 15, NULL); + nm_connectivity_lib_init (&priv->concheck, 15); #endif priv->state = NM_CONNECTIVITY_NONE; } @@ -451,12 +291,7 @@ dispose (GObject *object) 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); - } - - nm_clear_g_source (&priv->check_id); + nm_connectivity_lib_dispose (&priv->concheck); #endif G_OBJECT_CLASS (nm_connectivity_parent_class)->dispose (object); |