mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-08 21:10:25 +01:00
libcurl does not allow removing easy-handles from within a curl
callback.
That was already partly avoided for one handle alone. That is, when
a handle completed inside a libcurl callback, it would only invoke the
callback, but not yet delete it. However, that is not enough, because
from within a callback another handle can be cancelled, leading to
the removal of (the other) handle and a crash:
==24572== at 0x40319AB: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==24572== by 0x52DDAE5: Curl_close (url.c:392)
==24572== by 0x52EC02C: curl_easy_cleanup (easy.c:825)
==24572== by 0x5FDCD2: cb_data_free (nm-connectivity.c:215)
==24572== by 0x5FF6DE: nm_connectivity_check_cancel (nm-connectivity.c:585)
==24572== by 0x55F7F9: concheck_handle_complete (nm-device.c:2601)
==24572== by 0x574C12: concheck_cb (nm-device.c:2725)
==24572== by 0x5FD887: cb_data_invoke_callback (nm-connectivity.c:167)
==24572== by 0x5FD959: easy_header_cb (nm-connectivity.c:435)
==24572== by 0x52D73CB: chop_write (sendf.c:612)
==24572== by 0x52D73CB: Curl_client_write (sendf.c:668)
==24572== by 0x52D54ED: Curl_http_readwrite_headers (http.c:3904)
==24572== by 0x52E9EA7: readwrite_data (transfer.c:548)
==24572== by 0x52E9EA7: Curl_readwrite (transfer.c:1161)
==24572== by 0x52F4193: multi_runsingle (multi.c:1915)
==24572== by 0x52F5531: multi_socket (multi.c:2607)
==24572== by 0x52F5804: curl_multi_socket_action (multi.c:2771)
Fix that, by never invoking any callbacks when we are inside a libcurl
callback. Instead, the handle is marked for completion and queued. Later,
we complete all queue handles separately.
While at it, drop the @error argument from NMConnectivityCheckCallback.
It was only used to signal cancellation. Let's instead signal that via
status NM_CONNECTIVITY_CANCELLED.
https://bugzilla.gnome.org/show_bug.cgi?id=797136
https://bugs.launchpad.net/ubuntu/+source/network-manager/+bug/1792745
https://bugzilla.opensuse.org/show_bug.cgi?id=1107197
https://github.com/NetworkManager/NetworkManager/pull/207
Fixes: d8a31794c8
810 lines
25 KiB
C
810 lines
25 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
|
|
/* NetworkManager -- Network link manager
|
|
*
|
|
* 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) 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"
|
|
|
|
#include "nm-connectivity.h"
|
|
|
|
#include <string.h>
|
|
|
|
#if WITH_CONCHECK
|
|
#include <curl/curl.h>
|
|
#endif
|
|
|
|
#include "c-list/src/c-list.h"
|
|
#include "nm-config.h"
|
|
#include "NetworkManagerUtils.h"
|
|
|
|
#define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n"
|
|
|
|
/*****************************************************************************/
|
|
|
|
NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_state_to_string, int /*NMConnectivityState*/,
|
|
NM_UTILS_LOOKUP_DEFAULT_WARN ("???"),
|
|
NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_UNKNOWN, "UNKNOWN"),
|
|
NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_NONE, "NONE"),
|
|
NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_LIMITED, "LIMITED"),
|
|
NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_PORTAL, "PORTAL"),
|
|
NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FULL, "FULL"),
|
|
|
|
NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_ERROR, "ERROR"),
|
|
NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FAKE, "FAKE"),
|
|
NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_CANCELLED, "CANCELLED"),
|
|
NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_DISPOSING, "DISPOSING"),
|
|
);
|
|
|
|
const char *
|
|
nm_connectivity_state_to_string (NMConnectivityState state)
|
|
{
|
|
return _state_to_string (state);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
struct _NMConnectivityCheckHandle {
|
|
CList handles_lst;
|
|
NMConnectivity *self;
|
|
NMConnectivityCheckCallback callback;
|
|
gpointer user_data;
|
|
|
|
char *ifspec;
|
|
|
|
#if WITH_CONCHECK
|
|
struct {
|
|
char *response;
|
|
|
|
CURL *curl_ehandle;
|
|
struct curl_slist *request_headers;
|
|
|
|
GString *recv_msg;
|
|
} concheck;
|
|
#endif
|
|
|
|
const char *completed_log_message;
|
|
char *completed_log_message_free;
|
|
NMConnectivityState completed_state;
|
|
|
|
guint timeout_id;
|
|
};
|
|
|
|
enum {
|
|
CONFIG_CHANGED,
|
|
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
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
|
|
} NMConnectivityPrivate;
|
|
|
|
struct _NMConnectivity {
|
|
GObject parent;
|
|
NMConnectivityPrivate _priv;
|
|
};
|
|
|
|
struct _NMConnectivityClass {
|
|
GObjectClass parent;
|
|
};
|
|
|
|
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);
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define _NMLOG_DOMAIN LOGD_CONCHECK
|
|
#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "connectivity", __VA_ARGS__)
|
|
|
|
#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 ? &cb_data->ifspec[3] : NULL), \
|
|
NULL, \
|
|
"connectivity: (%s) " \
|
|
_NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
|
|
(cb_data->ifspec ? &cb_data->ifspec[3] : "") \
|
|
_NM_UTILS_MACRO_REST (__VA_ARGS__)); \
|
|
} \
|
|
} G_STMT_END
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
cb_data_complete (NMConnectivityCheckHandle *cb_data,
|
|
NMConnectivityState state,
|
|
const char *log_message)
|
|
{
|
|
NMConnectivity *self;
|
|
|
|
nm_assert (cb_data);
|
|
nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
|
|
nm_assert (cb_data->callback);
|
|
nm_assert (state != NM_CONNECTIVITY_UNKNOWN);
|
|
nm_assert (log_message);
|
|
|
|
self = cb_data->self;
|
|
|
|
/* mark the handle as completing. After this point, nm_connectivity_check_cancel()
|
|
* is no longer possible. */
|
|
cb_data->self = NULL;
|
|
|
|
c_list_unlink_stale (&cb_data->handles_lst);
|
|
|
|
#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
|
|
* why we queue completed handles in this case.
|
|
*
|
|
* cb_data_complete() is however only called *not* from within a
|
|
* libcurl callback. So, this is fine. */
|
|
curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_WRITEFUNCTION, NULL);
|
|
curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_WRITEDATA, NULL);
|
|
curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_HEADERFUNCTION, NULL);
|
|
curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_HEADERDATA, NULL);
|
|
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_easy_cleanup (cb_data->concheck.curl_ehandle);
|
|
|
|
curl_slist_free_all (cb_data->concheck.request_headers);
|
|
}
|
|
#endif
|
|
|
|
nm_clear_g_source (&cb_data->timeout_id);
|
|
|
|
_LOG2D ("check completed: %s; %s",
|
|
nm_connectivity_state_to_string (state),
|
|
log_message);
|
|
|
|
cb_data->callback (self,
|
|
cb_data,
|
|
state,
|
|
cb_data->user_data);
|
|
|
|
/* Note: self might be a danling pointer at this point. It must not be used
|
|
* after this point, and all callers must either take a reference first, or
|
|
* 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);
|
|
#endif
|
|
g_free (cb_data->ifspec);
|
|
if (cb_data->completed_log_message_free)
|
|
g_free (cb_data->completed_log_message_free);
|
|
g_slice_free (NMConnectivityCheckHandle, cb_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
#if WITH_CONCHECK
|
|
|
|
static void
|
|
cb_data_queue_completed (NMConnectivityCheckHandle *cb_data,
|
|
NMConnectivityState state,
|
|
const char *log_message_static,
|
|
char *log_message_take /* take */)
|
|
{
|
|
nm_assert (cb_data);
|
|
nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
|
|
nm_assert (state != NM_CONNECTIVITY_UNKNOWN);
|
|
nm_assert (log_message_static || log_message_take);
|
|
nm_assert (cb_data->completed_state == NM_CONNECTIVITY_UNKNOWN);
|
|
nm_assert (!cb_data->completed_log_message);
|
|
nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->handles_lst_head, &cb_data->handles_lst));
|
|
|
|
cb_data->completed_state = state;
|
|
cb_data->completed_log_message = log_message_static ?: log_message_take;
|
|
cb_data->completed_log_message_free = log_message_take;
|
|
|
|
c_list_unlink_stale (&cb_data->handles_lst);
|
|
c_list_link_tail (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->completed_handles_lst_head, &cb_data->handles_lst);
|
|
}
|
|
|
|
static void
|
|
_complete_queued (NMConnectivity *self)
|
|
{
|
|
NMConnectivity *self_keep_alive = NULL;
|
|
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
|
|
NMConnectivityCheckHandle *cb_data;
|
|
|
|
while ((cb_data = c_list_first_entry (&priv->completed_handles_lst_head, NMConnectivityCheckHandle, handles_lst))) {
|
|
if (!self_keep_alive)
|
|
self_keep_alive = g_object_ref (self);
|
|
cb_data_complete (cb_data,
|
|
cb_data->completed_state,
|
|
cb_data->completed_log_message);
|
|
}
|
|
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)
|
|
{
|
|
NMConnectivityCheckHandle *cb_data;
|
|
CURLMsg *msg;
|
|
CURLcode eret;
|
|
int m_left;
|
|
long response_code;
|
|
CURLMcode ret;
|
|
int running_handles;
|
|
gboolean success = TRUE;
|
|
|
|
ret = curl_multi_socket_action (mhandle, sockfd, ev_bitmask, &running_handles);
|
|
if (ret != CURLM_OK) {
|
|
_LOGD ("connectivity check failed: %d", ret);
|
|
success = FALSE;
|
|
}
|
|
|
|
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, (char **) &cb_data);
|
|
if (eret != CURLE_OK) {
|
|
_LOGD ("curl cannot extract cb_data for easy handle, skipping msg");
|
|
success = FALSE;
|
|
continue;
|
|
}
|
|
|
|
nm_assert (cb_data);
|
|
nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
|
|
|
|
if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) {
|
|
/* callback was already invoked earlier. Nothing to do. */
|
|
continue;
|
|
}
|
|
|
|
if (msg->data.result != CURLE_OK) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* if we return a failure, we don't know what went wrong. It's likely serious, because
|
|
* a failure here is not expected. Return FALSE, so that we stop polling the file descriptor.
|
|
* Worst case, this leaves the pending connectivity check unhandled, until our regular
|
|
* time-out kicks in. */
|
|
return success;
|
|
}
|
|
|
|
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);
|
|
|
|
priv->concheck.curl_timer = 0;
|
|
_con_curl_check_connectivity (priv->concheck.curl_mhandle, CURL_SOCKET_TIMEOUT, 0);
|
|
_complete_queued (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);
|
|
|
|
nm_clear_g_source (&priv->concheck.curl_timer);
|
|
if (timeout_ms != -1)
|
|
priv->concheck.curl_timer = g_timeout_add (timeout_ms, _con_curl_timeout_cb, self);
|
|
return 0;
|
|
}
|
|
|
|
typedef struct {
|
|
NMConnectivity *self;
|
|
GIOChannel *ch;
|
|
|
|
/* this is a very simplistic weak-pointer. If ConCurlSockData gets
|
|
* destroyed, it will set *destroy_notify to TRUE.
|
|
*
|
|
* _con_curl_socketevent_cb() uses this to detect whether it can
|
|
* safely access @fdp after _con_curl_check_connectivity(). */
|
|
gboolean *destroy_notify;
|
|
|
|
guint ev;
|
|
} ConCurlSockData;
|
|
|
|
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);
|
|
int fd = g_io_channel_unix_get_fd (ch);
|
|
int action = 0;
|
|
gboolean fdp_destroyed = FALSE;
|
|
gboolean success;
|
|
|
|
if (condition & G_IO_IN)
|
|
action |= CURL_CSELECT_IN;
|
|
if (condition & G_IO_OUT)
|
|
action |= CURL_CSELECT_OUT;
|
|
if (condition & G_IO_ERR)
|
|
action |= CURL_CSELECT_ERR;
|
|
|
|
nm_assert (!fdp->destroy_notify);
|
|
fdp->destroy_notify = &fdp_destroyed;
|
|
|
|
success = _con_curl_check_connectivity (priv->concheck.curl_mhandle, fd, action);
|
|
|
|
if (fdp_destroyed) {
|
|
/* hups. fdp got invalidated during _con_curl_check_connectivity(). That's fine,
|
|
* just don't touch it. */
|
|
} else {
|
|
nm_assert (fdp->destroy_notify == &fdp_destroyed);
|
|
fdp->destroy_notify = NULL;
|
|
if (!success)
|
|
fdp->ev = 0;
|
|
}
|
|
|
|
_complete_queued (self);
|
|
|
|
return success ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
|
|
}
|
|
|
|
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);
|
|
ConCurlSockData *fdp = socketp;
|
|
GIOCondition condition = 0;
|
|
|
|
(void) _NM_ENSURE_TYPE (int, fd);
|
|
|
|
if (what == CURL_POLL_REMOVE) {
|
|
if (fdp) {
|
|
if (fdp->destroy_notify)
|
|
*fdp->destroy_notify = TRUE;
|
|
curl_multi_assign (priv->concheck.curl_mhandle, fd, NULL);
|
|
nm_clear_g_source (&fdp->ev);
|
|
g_io_channel_unref (fdp->ch);
|
|
g_slice_free (ConCurlSockData, fdp);
|
|
}
|
|
} else {
|
|
if (!fdp) {
|
|
fdp = g_slice_new0 (ConCurlSockData);
|
|
fdp->self = self;
|
|
fdp->ch = g_io_channel_unix_new (fd);
|
|
curl_multi_assign (priv->concheck.curl_mhandle, fd, fdp);
|
|
} else
|
|
nm_clear_g_source (&fdp->ev);
|
|
|
|
if (what == CURL_POLL_IN)
|
|
condition = G_IO_IN;
|
|
else if (what == CURL_POLL_OUT)
|
|
condition = G_IO_OUT;
|
|
else if (what == CURL_POLL_INOUT)
|
|
condition = G_IO_IN | G_IO_OUT;
|
|
|
|
if (condition)
|
|
fdp->ev = g_io_add_watch (fdp->ch, condition, _con_curl_socketevent_cb, fdp);
|
|
}
|
|
|
|
return CURLM_OK;
|
|
}
|
|
|
|
static size_t
|
|
easy_header_cb (char *buffer, size_t size, size_t nitems, void *userdata)
|
|
{
|
|
NMConnectivityCheckHandle *cb_data = userdata;
|
|
size_t len = size * nitems;
|
|
|
|
if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) {
|
|
/* already completed. */
|
|
return 0;
|
|
}
|
|
|
|
if ( len >= sizeof (HEADER_STATUS_ONLINE) - 1
|
|
&& !g_ascii_strncasecmp (buffer, HEADER_STATUS_ONLINE, sizeof (HEADER_STATUS_ONLINE) - 1)) {
|
|
cb_data_queue_completed (cb_data,
|
|
NM_CONNECTIVITY_FULL,
|
|
"status header found",
|
|
NULL);
|
|
return 0;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static size_t
|
|
easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
|
|
{
|
|
NMConnectivityCheckHandle *cb_data = userdata;
|
|
size_t len = size * nmemb;
|
|
const char *response;
|
|
|
|
if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) {
|
|
/* already completed. */
|
|
return 0;
|
|
}
|
|
|
|
if (!cb_data->concheck.recv_msg)
|
|
cb_data->concheck.recv_msg = g_string_sized_new (len + 10);
|
|
|
|
g_string_append_len (cb_data->concheck.recv_msg, buffer, len);
|
|
|
|
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 {
|
|
cb_data_queue_completed (cb_data,
|
|
NM_CONNECTIVITY_PORTAL,
|
|
"unexpected response",
|
|
NULL);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static gboolean
|
|
_timeout_cb (gpointer user_data)
|
|
{
|
|
NMConnectivityCheckHandle *cb_data = user_data;
|
|
|
|
nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
|
|
nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->handles_lst_head, &cb_data->handles_lst));
|
|
|
|
cb_data_complete (cb_data, NM_CONNECTIVITY_LIMITED, "timeout");
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
_idle_cb (gpointer user_data)
|
|
{
|
|
NMConnectivityCheckHandle *cb_data = user_data;
|
|
|
|
nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
|
|
nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->handles_lst_head, &cb_data->handles_lst));
|
|
|
|
cb_data->timeout_id = 0;
|
|
if (!cb_data->ifspec) {
|
|
gs_free_error GError *error = NULL;
|
|
|
|
/* the invocation was with an invalid ifname. It is a fail. */
|
|
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
|
|
cb_data_complete (cb_data, NM_CONNECTIVITY_FAKE, "fake result");
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
NMConnectivityCheckHandle *
|
|
nm_connectivity_check_start (NMConnectivity *self,
|
|
const char *iface,
|
|
NMConnectivityCheckCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
NMConnectivityPrivate *priv;
|
|
NMConnectivityCheckHandle *cb_data;
|
|
|
|
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;
|
|
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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
_LOG2D ("start fake request");
|
|
cb_data->timeout_id = g_idle_add (_idle_cb, cb_data);
|
|
return cb_data;
|
|
}
|
|
|
|
void
|
|
nm_connectivity_check_cancel (NMConnectivityCheckHandle *cb_data)
|
|
{
|
|
g_return_if_fail (cb_data);
|
|
g_return_if_fail (NM_IS_CONNECTIVITY (cb_data->self));
|
|
|
|
nm_assert ( c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->handles_lst_head, &cb_data->handles_lst)
|
|
|| c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->completed_handles_lst_head, &cb_data->handles_lst));
|
|
|
|
cb_data_complete (cb_data, NM_CONNECTIVITY_CANCELLED, "cancelled");
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
gboolean
|
|
nm_connectivity_check_enabled (NMConnectivity *self)
|
|
{
|
|
g_return_val_if_fail (NM_IS_CONNECTIVITY (self), FALSE);
|
|
|
|
return NM_CONNECTIVITY_GET_PRIVATE (self)->enabled;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
guint
|
|
nm_connectivity_get_interval (NMConnectivity *self)
|
|
{
|
|
return nm_connectivity_check_enabled (self)
|
|
? NM_CONNECTIVITY_GET_PRIVATE (self)->interval
|
|
: 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
interval = MIN (interval, (7 * 24 * 3600));
|
|
if (priv->interval != interval) {
|
|
priv->interval = interval;
|
|
changed = TRUE;
|
|
}
|
|
|
|
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)
|
|
enabled = nm_config_data_get_connectivity_enabled (config_data);
|
|
#endif
|
|
|
|
if (priv->enabled != enabled) {
|
|
priv->enabled = enabled;
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
c_list_init (&priv->handles_lst_head);
|
|
c_list_init (&priv->completed_handles_lst_head);
|
|
|
|
priv->config = g_object_ref (nm_config_get ());
|
|
g_signal_connect (G_OBJECT (priv->config),
|
|
NM_CONFIG_SIGNAL_CONFIG_CHANGED,
|
|
G_CALLBACK (config_changed_cb),
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
update_config (self, nm_config_get_data (priv->config));
|
|
}
|
|
|
|
static void
|
|
dispose (GObject *object)
|
|
{
|
|
NMConnectivity *self = NM_CONNECTIVITY (object);
|
|
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
|
|
NMConnectivityCheckHandle *cb_data;
|
|
|
|
nm_assert (c_list_is_empty (&priv->completed_handles_lst_head));
|
|
|
|
while ((cb_data = c_list_first_entry (&priv->handles_lst_head,
|
|
NMConnectivityCheckHandle,
|
|
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);
|
|
|
|
#if WITH_CONCHECK
|
|
nm_clear_g_source (&priv->concheck.curl_timer);
|
|
|
|
curl_multi_cleanup (priv->concheck.curl_mhandle);
|
|
curl_global_cleanup ();
|
|
#endif
|
|
|
|
if (priv->config) {
|
|
g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
|
|
g_clear_object (&priv->config);
|
|
}
|
|
|
|
G_OBJECT_CLASS (nm_connectivity_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
nm_connectivity_class_init (NMConnectivityClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
signals[CONFIG_CHANGED] =
|
|
g_signal_new (NM_CONNECTIVITY_CONFIG_CHANGED,
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
0, NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
|
|
object_class->dispose = dispose;
|
|
}
|