NetworkManager/src/nm-connectivity.c
Thomas Haller c1054ec8ff connectivity: always build nm-connectivity.c source
We already do conditional build with "#if WITH_CONCHECK".
Get rid of the conditional in the makefile and instead do
conditional compilating inside the source file "nm-connectivity.c".

The advantage is, now if you want to know which parts are build,
you only need to grep for the WITH_CONCHECK preprocessor define
instead of also caring about the conditional in Makefile.am and
meson.build.

It doesn't change the fact of conditional compilation. But it
consistently uses one mechanism to achieve it.
2018-03-19 14:46:55 +01:00

592 lines
18 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 "nm-config.h"
#include "NetworkManagerUtils.h"
/*****************************************************************************/
NM_UTILS_LOOKUP_STR_DEFINE (nm_connectivity_state_to_string, 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"),
);
/*****************************************************************************/
#if WITH_CONCHECK
typedef struct {
GSimpleAsyncResult *simple;
char *response;
CURL *curl_ehandle;
size_t msg_size;
char *msg;
struct curl_slist *request_headers;
guint timeout_id;
char *ifspec;
} NMConnectivityCheckHandle;
enum {
PERIODIC_CHECK,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
typedef struct {
char *uri;
char *response;
gboolean enabled;
guint interval;
NMConfig *config;
guint periodic_check_id;
CURLM *curl_mhandle;
guint curl_timer;
} 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[3], NULL, \
"connectivity: (%s) " \
_NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
&cb_data->ifspec[3] \
_NM_UTILS_MACRO_REST (__VA_ARGS__)); \
} \
} G_STMT_END
/*****************************************************************************/
static void
finish_cb_data (NMConnectivityCheckHandle *cb_data, NMConnectivityState new_state)
{
/* Contrary to what cURL manual claim it is *not* safe to remove
* the easy handle "at any moment"; specifically not from the
* write function. Thus here we just dissociate the cb_data from
* the easy handle and the easy handle will be cleaned up when the
* message goes to CURLMSG_DONE in curl_check_connectivity(). */
curl_easy_setopt (cb_data->curl_ehandle, CURLOPT_PRIVATE, NULL);
g_simple_async_result_set_op_res_gssize (cb_data->simple, new_state);
g_simple_async_result_complete (cb_data->simple);
g_object_unref (cb_data->simple);
curl_slist_free_all (cb_data->request_headers);
g_free (cb_data->response);
g_free (cb_data->msg);
g_free (cb_data->ifspec);
g_source_remove (cb_data->timeout_id);
g_slice_free (NMConnectivityCheckHandle, cb_data);
}
static void
curl_check_connectivity (CURLM *mhandle, CURLMcode ret)
{
NMConnectivityCheckHandle *cb_data;
CURLMsg *msg;
CURLcode eret;
CURL *easy_handle;
gint m_left;
long response_code;
if (ret != CURLM_OK)
_LOGW ("connectivity check failed");
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) {
_LOG2E ("curl cannot extract cb_data for easy handle %p, skipping msg", msg->easy_handle);
continue;
}
if (cb_data) {
NMConnectivityState c;
/* If cb_data is still there this message hasn't been
* taken care of. Do so now. */
if (msg->data.result != CURLE_OK) {
_LOG2D ("check failed (%d)", msg->data.result);
c = NM_CONNECTIVITY_LIMITED;
} else if ( !cb_data->response[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. */
_LOG2D ("response with no content received, check successful");
c = NM_CONNECTIVITY_FULL;
} 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 */
_LOG2I ("response did not match expected response '%s'; assuming captive portal.",
cb_data->response);
c = NM_CONNECTIVITY_PORTAL;
}
finish_cb_data (cb_data, c);
}
/* Do not use message data after calling curl_multi_remove_handle() */
easy_handle = msg->easy_handle;
curl_multi_remove_handle (mhandle, easy_handle);
curl_easy_cleanup (easy_handle);
}
}
static gboolean
curl_timeout_cb (gpointer user_data)
{
NMConnectivity *self = NM_CONNECTIVITY (user_data);
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
CURLMcode ret;
int pending_conn;
priv->curl_timer = 0;
ret = curl_multi_socket_action (priv->curl_mhandle, CURL_SOCKET_TIMEOUT, 0, &pending_conn);
_LOGT ("timeout elapsed - multi_socket_action (%d conn remaining)", pending_conn);
curl_check_connectivity (priv->curl_mhandle, ret);
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->curl_timer);
if (timeout_ms != -1)
priv->curl_timer = g_timeout_add (timeout_ms, curl_timeout_cb, self);
return 0;
}
static gboolean
curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer data)
{
NMConnectivity *self = NM_CONNECTIVITY (data);
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
CURLMcode ret;
int pending_conn = 0;
gboolean bret = TRUE;
int fd = g_io_channel_unix_get_fd (ch);
int action = 0;
if (condition & G_IO_IN)
action |= CURL_CSELECT_IN;
if (condition & G_IO_OUT)
action |= CURL_CSELECT_OUT;
ret = curl_multi_socket_action (priv->curl_mhandle, fd, 0, &pending_conn);
curl_check_connectivity (priv->curl_mhandle, ret);
if (pending_conn == 0) {
nm_clear_g_source (&priv->curl_timer);
bret = FALSE;
}
return bret;
}
typedef struct {
GIOChannel *ch;
guint ev;
} CurlSockData;
static int
multi_socket_cb (CURL *e_handle, curl_socket_t s, int what, void *userdata, void *socketp)
{
NMConnectivity *self = NM_CONNECTIVITY (userdata);
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
CurlSockData *fdp = (CurlSockData *) socketp;
GIOCondition condition = 0;
if (what == CURL_POLL_REMOVE) {
if (fdp) {
nm_clear_g_source (&fdp->ev);
g_io_channel_unref (fdp->ch);
g_slice_free (CurlSockData, fdp);
}
} else {
if (!fdp) {
fdp = g_slice_new0 (CurlSockData);
fdp->ch = g_io_channel_unix_new (s);
} 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 (condition == CURL_POLL_INOUT)
condition = G_IO_IN | G_IO_OUT;
if (condition)
fdp->ev = g_io_add_watch (fdp->ch, condition, curl_socketevent_cb, self);
curl_multi_assign (priv->curl_mhandle, s, fdp);
}
return CURLM_OK;
}
#define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n"
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 ( len >= sizeof (HEADER_STATUS_ONLINE) - 1
&& !g_ascii_strncasecmp (buffer, HEADER_STATUS_ONLINE, sizeof (HEADER_STATUS_ONLINE) - 1)) {
_LOG2D ("status header found, check successful");
finish_cb_data (cb_data, NM_CONNECTIVITY_FULL);
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;
cb_data->msg = g_realloc (cb_data->msg, cb_data->msg_size + len);
memcpy (cb_data->msg + cb_data->msg_size, buffer, len);
cb_data->msg_size += len;
/* Check matching prefix if a expected response is given */
if ( cb_data->response[0]
&& cb_data->msg_size >= strlen (cb_data->response)) {
/* We already have enough data -- check response */
if (g_str_has_prefix (cb_data->msg, cb_data->response)) {
_LOG2D ("check successful.");
finish_cb_data (cb_data, NM_CONNECTIVITY_FULL);
} else {
_LOG2I ("response did not match expected response '%s'; assuming captive portal.",
cb_data->response);
finish_cb_data (cb_data, NM_CONNECTIVITY_PORTAL);
}
return 0;
}
return len;
}
static gboolean
timeout_cb (gpointer user_data)
{
NMConnectivityCheckHandle *cb_data = user_data;
NMConnectivity *self = NM_CONNECTIVITY (g_async_result_get_source_object (G_ASYNC_RESULT (cb_data->simple)));
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
CURL *ehandle = cb_data->curl_ehandle;
_LOG2I ("timed out");
finish_cb_data (cb_data, NM_CONNECTIVITY_LIMITED);
curl_multi_remove_handle (priv->curl_mhandle, ehandle);
curl_easy_cleanup (ehandle);
return G_SOURCE_REMOVE;
}
void
nm_connectivity_check_async (NMConnectivity *self,
const char *iface,
GAsyncReadyCallback callback,
gpointer user_data)
{
NMConnectivityPrivate *priv;
GSimpleAsyncResult *simple;
CURL *ehandle = NULL;
g_return_if_fail (NM_IS_CONNECTIVITY (self));
priv = NM_CONNECTIVITY_GET_PRIVATE (self);
simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
nm_connectivity_check_async);
if (priv->enabled)
ehandle = curl_easy_init ();
if (ehandle) {
NMConnectivityCheckHandle *cb_data = g_slice_new0 (NMConnectivityCheckHandle);
cb_data->curl_ehandle = ehandle;
cb_data->request_headers = curl_slist_append (NULL, "Connection: close");
cb_data->ifspec = g_strdup_printf ("if!%s", iface);
cb_data->simple = simple;
if (priv->response)
cb_data->response = g_strdup (priv->response);
else
cb_data->response = g_strdup (NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE);
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->request_headers);
curl_easy_setopt (ehandle, CURLOPT_INTERFACE, cb_data->ifspec);
curl_multi_add_handle (priv->curl_mhandle, ehandle);
cb_data->timeout_id = g_timeout_add_seconds (30, timeout_cb, cb_data);
_LOG2D ("sending request to '%s'", priv->uri);
return;
} else {
_LOGD ("(%s) faking request. Connectivity check disabled", iface);
}
g_simple_async_result_set_op_res_gssize (simple, NM_CONNECTIVITY_UNKNOWN);
g_simple_async_result_complete_in_idle (simple);
g_object_unref (simple);
}
NMConnectivityState
nm_connectivity_check_finish (NMConnectivity *self,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *simple;
g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), nm_connectivity_check_async), NM_CONNECTIVITY_UNKNOWN);
simple = G_SIMPLE_ASYNC_RESULT (result);
if (g_simple_async_result_propagate_error (simple, error))
return NM_CONNECTIVITY_UNKNOWN;
return (NMConnectivityState) g_simple_async_result_get_op_res_gssize (simple);
}
gboolean
nm_connectivity_check_enabled (NMConnectivity *self)
{
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
return priv->enabled;
}
/*****************************************************************************/
static gboolean
periodic_check (gpointer user_data)
{
g_signal_emit (NM_CONNECTIVITY (user_data), signals[PERIODIC_CHECK], 0);
return G_SOURCE_CONTINUE;
}
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 does'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);
if (priv->interval != interval) {
priv->interval = interval;
changed = TRUE;
}
/* Set enabled flag. */
enabled = nm_config_data_get_connectivity_enabled (config_data);
/* connectivity checking also requires a valid URI, interval and
* curl_mhandle */
if (!(priv->uri && priv->interval && priv->curl_mhandle)) {
enabled = FALSE;
}
if (priv->enabled != enabled) {
priv->enabled = enabled;
changed = TRUE;
}
/* Set the response. */
response = nm_config_data_get_connectivity_response (config_data);
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);
changed = TRUE;
}
if (changed) {
nm_clear_g_source (&priv->periodic_check_id);
if (nm_connectivity_check_enabled (self))
priv->periodic_check_id = g_timeout_add_seconds (priv->interval, periodic_check, self);
}
}
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);
CURLcode retv;
retv = curl_global_init (CURL_GLOBAL_ALL);
if (retv == CURLE_OK)
priv->curl_mhandle = curl_multi_init ();
if (!priv->curl_mhandle)
_LOGE ("unable to init cURL, connectivity check will not work");
else {
curl_multi_setopt (priv->curl_mhandle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
curl_multi_setopt (priv->curl_mhandle, CURLMOPT_SOCKETDATA, self);
curl_multi_setopt (priv->curl_mhandle, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
curl_multi_setopt (priv->curl_mhandle, CURLMOPT_TIMERDATA, self);
curl_multi_setopt (priv->curl_mhandle, CURLOPT_VERBOSE, 1);
}
priv->config = g_object_ref (nm_config_get ());
update_config (self, nm_config_get_data (priv->config));
g_signal_connect (G_OBJECT (priv->config),
NM_CONFIG_SIGNAL_CONFIG_CHANGED,
G_CALLBACK (config_changed_cb),
self);
}
static void
dispose (GObject *object)
{
NMConnectivity *self = NM_CONNECTIVITY (object);
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
g_clear_pointer (&priv->uri, g_free);
g_clear_pointer (&priv->response, g_free);
if (priv->config) {
g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
g_clear_object (&priv->config);
}
curl_multi_cleanup (priv->curl_mhandle);
curl_global_cleanup ();
nm_clear_g_source (&priv->periodic_check_id);
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[PERIODIC_CHECK] =
g_signal_new (NM_CONNECTIVITY_PERIODIC_CHECK,
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
object_class->dispose = dispose;
}
#endif /* WITH_CONCHECK */