2011-10-21 21:21:30 +02:00
|
|
|
/* -*- 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>
|
2011-12-05 14:48:47 -06:00
|
|
|
* Copyright (C) 2011 Dan Williams <dcbw@redhat.com>
|
2016-04-04 18:23:13 +02:00
|
|
|
* Copyright (C) 2016,2017 Red Hat, Inc.
|
2011-10-21 21:21:30 +02:00
|
|
|
*/
|
|
|
|
|
|
2016-02-19 14:57:48 +01:00
|
|
|
#include "nm-default.h"
|
2011-10-21 21:21:30 +02:00
|
|
|
|
2016-09-29 13:49:01 +02:00
|
|
|
#include "nm-connectivity.h"
|
|
|
|
|
|
2011-10-21 21:21:30 +02:00
|
|
|
#include <string.h>
|
2013-07-29 16:36:00 -04:00
|
|
|
#if WITH_CONCHECK
|
2016-04-04 18:23:13 +02:00
|
|
|
#include <curl/curl.h>
|
2013-07-29 16:36:00 -04:00
|
|
|
#endif
|
2011-10-21 21:21:30 +02:00
|
|
|
|
2015-02-19 14:24:37 +01:00
|
|
|
#include "nm-config.h"
|
2016-07-19 16:05:35 +01:00
|
|
|
#include "nm-dispatcher.h"
|
2016-02-01 13:32:02 +01:00
|
|
|
#include "NetworkManagerUtils.h"
|
2011-10-21 21:21:30 +02:00
|
|
|
|
2016-09-29 13:49:01 +02:00
|
|
|
/*****************************************************************************/
|
2011-12-13 17:35:36 -06:00
|
|
|
|
2016-09-29 13:49:01 +02:00
|
|
|
NM_GOBJECT_PROPERTIES_DEFINE (NMConnectivity,
|
|
|
|
|
PROP_URI,
|
|
|
|
|
PROP_INTERVAL,
|
|
|
|
|
PROP_RESPONSE,
|
|
|
|
|
PROP_STATE,
|
|
|
|
|
);
|
2015-01-19 19:06:27 +01:00
|
|
|
|
2011-10-21 21:21:30 +02:00
|
|
|
typedef struct {
|
2011-12-13 17:35:36 -06:00
|
|
|
char *uri;
|
|
|
|
|
char *response;
|
2013-07-29 09:42:44 -04:00
|
|
|
guint interval;
|
2014-07-14 13:40:30 +02:00
|
|
|
gboolean online; /* whether periodic connectivity checking is enabled. */
|
2013-07-29 09:42:44 -04:00
|
|
|
|
2013-07-29 16:36:00 -04:00
|
|
|
#if WITH_CONCHECK
|
2016-04-04 18:23:13 +02:00
|
|
|
CURLM *curl_mhandle;
|
|
|
|
|
guint curl_timer;
|
2014-07-14 13:40:30 +02:00
|
|
|
gboolean initial_check_obsoleted;
|
2011-12-13 17:35:36 -06:00
|
|
|
guint check_id;
|
2013-07-29 16:36:00 -04:00
|
|
|
#endif
|
2013-07-29 09:42:44 -04:00
|
|
|
|
2013-07-30 16:31:31 -04:00
|
|
|
NMConnectivityState state;
|
2011-10-21 21:21:30 +02:00
|
|
|
} NMConnectivityPrivate;
|
|
|
|
|
|
2016-09-29 13:49:01 +02:00
|
|
|
struct _NMConnectivity {
|
|
|
|
|
GObject parent;
|
|
|
|
|
NMConnectivityPrivate _priv;
|
2011-10-21 21:21:30 +02:00
|
|
|
};
|
|
|
|
|
|
2016-09-29 13:49:01 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
2016-10-14 15:32:56 +02:00
|
|
|
#define _NMLOG_DOMAIN LOGD_CONCHECK
|
|
|
|
|
#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "connectivity", __VA_ARGS__)
|
2016-09-29 13:49:01 +02:00
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
2011-10-21 21:21:30 +02:00
|
|
|
|
2013-07-30 16:31:31 -04:00
|
|
|
NMConnectivityState
|
|
|
|
|
nm_connectivity_get_state (NMConnectivity *connectivity)
|
2011-12-05 12:29:55 -06:00
|
|
|
{
|
2013-07-30 16:31:31 -04:00
|
|
|
g_return_val_if_fail (NM_IS_CONNECTIVITY (connectivity), NM_CONNECTIVITY_UNKNOWN);
|
2011-12-05 12:29:55 -06:00
|
|
|
|
2013-07-30 16:31:31 -04:00
|
|
|
return NM_CONNECTIVITY_GET_PRIVATE (connectivity)->state;
|
2011-10-21 21:21:30 +02:00
|
|
|
}
|
|
|
|
|
|
2016-02-01 13:32:02 +01:00
|
|
|
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"),
|
|
|
|
|
);
|
2015-01-13 15:08:21 -05:00
|
|
|
|
2011-12-14 13:41:57 -06:00
|
|
|
static void
|
2013-07-30 16:31:31 -04:00
|
|
|
update_state (NMConnectivity *self, NMConnectivityState state)
|
2011-12-14 13:41:57 -06:00
|
|
|
{
|
|
|
|
|
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
|
|
|
|
|
|
2013-07-30 16:31:31 -04:00
|
|
|
if (priv->state != state) {
|
2015-01-19 19:06:27 +01:00
|
|
|
_LOGD ("state changed from %s to %s",
|
|
|
|
|
nm_connectivity_state_to_string (priv->state),
|
|
|
|
|
nm_connectivity_state_to_string (state));
|
2013-07-30 16:31:31 -04:00
|
|
|
priv->state = state;
|
2016-09-29 13:49:01 +02:00
|
|
|
_notify (self, PROP_STATE);
|
2016-07-19 16:05:35 +01:00
|
|
|
|
2017-03-08 14:11:22 +01:00
|
|
|
nm_dispatcher_call_connectivity (state, NULL, NULL, NULL);
|
2013-07-30 16:31:31 -04:00
|
|
|
}
|
2011-12-14 13:41:57 -06:00
|
|
|
}
|
|
|
|
|
|
2016-04-04 18:23:13 +02:00
|
|
|
/*****************************************************************************/
|
2014-07-14 13:40:30 +02:00
|
|
|
|
2016-04-04 18:23:13 +02:00
|
|
|
#if WITH_CONCHECK
|
2013-07-31 09:14:39 -04:00
|
|
|
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) {
|
2015-01-19 19:06:27 +01:00
|
|
|
_LOGE ("check failed: %s", error->message);
|
2013-07-31 09:14:39 -04:00
|
|
|
g_error_free (error);
|
|
|
|
|
}
|
2011-10-21 21:21:30 +02:00
|
|
|
}
|
|
|
|
|
|
2011-12-14 13:41:57 -06:00
|
|
|
static gboolean
|
|
|
|
|
run_check (gpointer user_data)
|
2011-10-21 21:21:30 +02:00
|
|
|
{
|
2011-12-14 13:41:57 -06:00
|
|
|
NMConnectivity *self = NM_CONNECTIVITY (user_data);
|
|
|
|
|
|
2013-07-31 09:14:39 -04:00
|
|
|
nm_connectivity_check_async (self, run_check_complete, NULL);
|
2013-07-29 09:42:44 -04:00
|
|
|
return TRUE;
|
2011-12-14 13:41:57 -06:00
|
|
|
}
|
2015-01-13 15:25:31 -05:00
|
|
|
|
|
|
|
|
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);
|
2014-07-14 13:40:30 +02:00
|
|
|
if (!priv->initial_check_obsoleted)
|
2015-01-13 15:25:31 -05:00
|
|
|
run_check (self);
|
|
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2013-07-29 16:36:00 -04:00
|
|
|
#endif
|
2011-10-21 21:21:30 +02:00
|
|
|
|
2014-07-14 13:40:30 +02:00
|
|
|
static void
|
|
|
|
|
_reschedule_periodic_checks (NMConnectivity *self, gboolean force_reschedule)
|
2011-12-14 13:41:57 -06:00
|
|
|
{
|
|
|
|
|
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
|
|
|
|
|
|
2015-01-13 15:08:21 -05:00
|
|
|
#if WITH_CONCHECK
|
2014-07-14 13:40:30 +02:00
|
|
|
if (priv->online && priv->uri && priv->interval) {
|
|
|
|
|
if (force_reschedule || !priv->check_id) {
|
|
|
|
|
if (priv->check_id)
|
|
|
|
|
g_source_remove (priv->check_id);
|
2015-01-13 15:25:31 -05:00
|
|
|
priv->check_id = g_timeout_add (0, idle_start_periodic_checks, self);
|
2014-07-14 13:40:30 +02:00
|
|
|
priv->initial_check_obsoleted = FALSE;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2016-01-04 09:46:22 +01:00
|
|
|
nm_clear_g_source (&priv->check_id);
|
2011-10-21 21:21:30 +02:00
|
|
|
}
|
2014-07-14 13:40:30 +02:00
|
|
|
if (priv->check_id)
|
|
|
|
|
return;
|
2013-07-29 16:36:00 -04:00
|
|
|
#endif
|
2013-07-31 09:14:39 -04:00
|
|
|
|
|
|
|
|
/* Either @online is %TRUE but we aren't checking connectivity, or
|
|
|
|
|
* @online is %FALSE. Either way we can update our status immediately.
|
|
|
|
|
*/
|
2014-07-14 13:40:30 +02:00
|
|
|
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) {
|
2015-01-19 19:06:27 +01:00
|
|
|
_LOGD ("set %s", online ? "online" : "offline");
|
2014-07-14 13:40:30 +02:00
|
|
|
priv->online = online;
|
|
|
|
|
_reschedule_periodic_checks (self, FALSE);
|
|
|
|
|
}
|
2011-10-21 21:21:30 +02:00
|
|
|
}
|
|
|
|
|
|
2016-04-04 18:23:13 +02:00
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#if WITH_CONCHECK
|
|
|
|
|
typedef struct {
|
|
|
|
|
GSimpleAsyncResult *simple;
|
|
|
|
|
char *uri;
|
|
|
|
|
char *response;
|
|
|
|
|
guint check_id_when_scheduled;
|
|
|
|
|
CURL *curl_ehandle;
|
|
|
|
|
size_t msg_size;
|
|
|
|
|
char *msg;
|
|
|
|
|
struct curl_slist *request_headers;
|
|
|
|
|
gboolean online_header;
|
|
|
|
|
} ConCheckCbData;
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
curl_check_connectivity (CURLM *mhandle, CURLMcode ret)
|
|
|
|
|
{
|
|
|
|
|
NMConnectivity *self;
|
|
|
|
|
NMConnectivityPrivate *priv;
|
|
|
|
|
NMConnectivityState new_state = NM_CONNECTIVITY_UNKNOWN;
|
|
|
|
|
ConCheckCbData *cb_data;
|
|
|
|
|
CURLMsg *msg;
|
|
|
|
|
CURLcode eret;
|
|
|
|
|
gint m_left;
|
|
|
|
|
|
|
|
|
|
_LOGT ("curl_multi check for easy messages");
|
|
|
|
|
if (ret != CURLM_OK)
|
|
|
|
|
_LOGE ("Connectivity check failed");
|
|
|
|
|
|
|
|
|
|
while ((msg = curl_multi_info_read (mhandle, &m_left))) {
|
|
|
|
|
_LOGT ("curl MSG received - ehandle:%p, type:%d", msg->easy_handle, msg->msg);
|
|
|
|
|
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, &cb_data);
|
|
|
|
|
if (eret != CURLE_OK) {
|
|
|
|
|
_LOGE ("curl cannot extract cb_data for easy handle %p, skipping msg", msg->easy_handle);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
self = NM_CONNECTIVITY (g_async_result_get_source_object (G_ASYNC_RESULT (cb_data->simple)));
|
|
|
|
|
priv = NM_CONNECTIVITY_GET_PRIVATE (self);
|
|
|
|
|
|
|
|
|
|
if (msg->data.result != CURLE_OK) {
|
|
|
|
|
_LOGD ("Check for uri '%s' failed", cb_data->uri);
|
|
|
|
|
new_state = NM_CONNECTIVITY_LIMITED;
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cb_data->online_header) {
|
|
|
|
|
_LOGD ("check for uri '%s' with Status header successful.", cb_data->uri);
|
|
|
|
|
new_state = NM_CONNECTIVITY_FULL;
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check response */
|
|
|
|
|
if (cb_data->msg && g_str_has_prefix (cb_data->msg, cb_data->response)) {
|
|
|
|
|
_LOGD ("Check for uri '%s' successful.", cb_data->uri);
|
|
|
|
|
new_state = NM_CONNECTIVITY_FULL;
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_LOGI ("Check for uri '%s' did not match expected response '%s'; assuming captive portal.",
|
|
|
|
|
cb_data->uri, cb_data->response);
|
|
|
|
|
new_state = NM_CONNECTIVITY_PORTAL;
|
|
|
|
|
cleanup:
|
|
|
|
|
/* 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)) {
|
|
|
|
|
_LOGT ("Update to connectivity state %s",
|
|
|
|
|
nm_connectivity_state_to_string (new_state));
|
|
|
|
|
update_state (self, new_state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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_multi_remove_handle (mhandle, cb_data->curl_ehandle);
|
|
|
|
|
curl_easy_cleanup (cb_data->curl_ehandle);
|
|
|
|
|
curl_slist_free_all (cb_data->request_headers);
|
|
|
|
|
g_free (cb_data->uri);
|
|
|
|
|
g_free (cb_data->response);
|
|
|
|
|
g_slice_free (ConCheckCbData, cb_data);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2017-03-22 18:48:58 +00:00
|
|
|
return G_SOURCE_REMOVE;
|
2016-04-04 18:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
multi_timer_cb (CURLM *multi, long timeout_ms, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
NMConnectivity *self = NM_CONNECTIVITY (userdata);
|
|
|
|
|
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
|
|
|
|
|
|
|
|
|
|
_LOGT ("curl_multi timer invocation --> timeout ms: %ld", timeout_ms);
|
|
|
|
|
|
|
|
|
|
nm_clear_g_source (&priv->curl_timer);
|
|
|
|
|
if (timeout_ms != -1)
|
|
|
|
|
priv->curl_timer = g_timeout_add (timeout_ms * 1000, 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);
|
|
|
|
|
_LOGT ("activity on monitored fd %d - multi_socket_action (%d conn remaining)", fd, 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;
|
|
|
|
|
|
|
|
|
|
_LOGT ("curl_multi socket callback --> socket %d", s);
|
|
|
|
|
|
|
|
|
|
if (what == CURL_POLL_REMOVE) {
|
|
|
|
|
if (fdp) {
|
|
|
|
|
_LOGT ("remove socket s=%d", s);
|
|
|
|
|
nm_clear_g_source (&fdp->ev);
|
|
|
|
|
g_io_channel_unref (fdp->ch);
|
|
|
|
|
g_slice_free (CurlSockData, fdp);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!fdp) {
|
|
|
|
|
_LOGT ("register new socket s=%d", s);
|
|
|
|
|
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;
|
|
|
|
|
if (what & CURL_POLL_OUT)
|
|
|
|
|
condition |= G_IO_OUT;
|
|
|
|
|
|
|
|
|
|
fdp->ev = g_io_add_watch (fdp->ch, condition, curl_socketevent_cb, self);
|
|
|
|
|
curl_multi_assign (priv->curl_mhandle, s, fdp);
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 18:48:58 +00:00
|
|
|
return CURLM_OK;
|
2016-04-04 18:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#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)
|
|
|
|
|
{
|
|
|
|
|
ConCheckCbData *cb_data = userdata;
|
|
|
|
|
size_t len = size * nitems;
|
|
|
|
|
|
|
|
|
|
_LOGT ("Received %lu header bytes from cURL\n", len);
|
|
|
|
|
|
|
|
|
|
if ( len >= sizeof (HEADER_STATUS_ONLINE) - 1
|
|
|
|
|
&& !g_ascii_strncasecmp (buffer, HEADER_STATUS_ONLINE, sizeof (HEADER_STATUS_ONLINE) - 1)) {
|
|
|
|
|
cb_data->online_header = TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
|
easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
ConCheckCbData *cb_data = userdata;
|
|
|
|
|
size_t len = size * nmemb;
|
|
|
|
|
|
|
|
|
|
_LOGT ("Received %lu body bytes from cURL\n", len);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
return len;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#define IS_PERIODIC_CHECK(callback) ((callback) == run_check_complete)
|
|
|
|
|
|
2011-12-14 13:41:57 -06:00
|
|
|
void
|
2013-07-31 09:14:39 -04:00
|
|
|
nm_connectivity_check_async (NMConnectivity *self,
|
|
|
|
|
GAsyncReadyCallback callback,
|
|
|
|
|
gpointer user_data)
|
2011-12-13 17:35:36 -06:00
|
|
|
{
|
2013-07-31 09:14:39 -04:00
|
|
|
NMConnectivityPrivate *priv;
|
|
|
|
|
GSimpleAsyncResult *simple;
|
2016-04-04 18:23:13 +02:00
|
|
|
#if WITH_CONCHECK
|
|
|
|
|
CURL *ehandle = NULL;
|
|
|
|
|
#endif
|
2011-12-14 13:41:57 -06:00
|
|
|
|
2013-08-29 10:06:38 +02:00
|
|
|
g_return_if_fail (NM_IS_CONNECTIVITY (self));
|
2013-07-31 09:14:39 -04:00
|
|
|
priv = NM_CONNECTIVITY_GET_PRIVATE (self);
|
2011-12-14 13:41:57 -06:00
|
|
|
|
2013-07-31 09:14:39 -04:00
|
|
|
simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
|
|
|
|
|
nm_connectivity_check_async);
|
|
|
|
|
|
|
|
|
|
#if WITH_CONCHECK
|
2016-04-04 18:23:13 +02:00
|
|
|
if (priv->uri && priv->interval && priv->curl_mhandle)
|
|
|
|
|
ehandle = curl_easy_init ();
|
|
|
|
|
|
|
|
|
|
if (ehandle) {
|
|
|
|
|
ConCheckCbData *cb_data = g_slice_new0 (ConCheckCbData);
|
|
|
|
|
|
|
|
|
|
cb_data->curl_ehandle = ehandle;
|
|
|
|
|
cb_data->request_headers = curl_slist_append (NULL, "Connection: close");
|
2014-07-14 13:40:30 +02:00
|
|
|
cb_data->simple = simple;
|
|
|
|
|
cb_data->uri = g_strdup (priv->uri);
|
2016-04-04 18:23:13 +02:00
|
|
|
if (priv->response)
|
|
|
|
|
cb_data->response = g_strdup (priv->response);
|
|
|
|
|
else
|
|
|
|
|
cb_data->response = g_strdup (NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE);
|
2014-07-14 13:40:30 +02:00
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
2016-04-04 18:23:13 +02:00
|
|
|
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_CONNECTTIMEOUT, 30);
|
|
|
|
|
curl_easy_setopt (ehandle, CURLOPT_LOW_SPEED_LIMIT, 1);
|
|
|
|
|
curl_easy_setopt (ehandle, CURLOPT_LOW_SPEED_TIME, 30);
|
|
|
|
|
curl_multi_add_handle (priv->curl_mhandle, ehandle);
|
|
|
|
|
|
2014-07-14 13:40:30 +02:00
|
|
|
priv->initial_check_obsoleted = TRUE;
|
2013-07-31 09:14:39 -04:00
|
|
|
|
2016-04-04 18:23:13 +02:00
|
|
|
_LOGD ("check: send %s request to '%s'", IS_PERIODIC_CHECK (callback) ? "periodic " : "", priv->uri);
|
2013-07-31 09:14:39 -04:00
|
|
|
return;
|
2014-07-14 13:40:30 +02:00
|
|
|
} else {
|
|
|
|
|
g_warn_if_fail (!IS_PERIODIC_CHECK (callback));
|
2015-01-19 19:06:27 +01:00
|
|
|
_LOGD ("check: faking request. Connectivity check disabled");
|
2013-07-31 09:14:39 -04:00
|
|
|
}
|
2014-07-14 13:40:30 +02:00
|
|
|
#else
|
2015-01-19 19:06:27 +01:00
|
|
|
_LOGD ("check: faking request. Compiled without connectivity-check support");
|
2013-07-29 16:36:00 -04:00
|
|
|
#endif
|
2013-07-31 09:14:39 -04:00
|
|
|
|
2013-07-30 16:31:31 -04:00
|
|
|
g_simple_async_result_set_op_res_gssize (simple, priv->state);
|
2013-07-31 09:14:39 -04:00
|
|
|
g_simple_async_result_complete_in_idle (simple);
|
2015-02-18 16:13:57 +01:00
|
|
|
g_object_unref (simple);
|
2011-12-13 17:35:36 -06:00
|
|
|
}
|
2011-10-21 21:21:30 +02:00
|
|
|
|
2013-07-30 16:31:31 -04:00
|
|
|
NMConnectivityState
|
2013-07-31 09:14:39 -04:00
|
|
|
nm_connectivity_check_finish (NMConnectivity *self,
|
|
|
|
|
GAsyncResult *result,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
GSimpleAsyncResult *simple;
|
|
|
|
|
|
2013-07-30 16:31:31 -04:00
|
|
|
g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), nm_connectivity_check_async), NM_CONNECTIVITY_UNKNOWN);
|
2013-07-31 09:14:39 -04:00
|
|
|
|
|
|
|
|
simple = G_SIMPLE_ASYNC_RESULT (result);
|
|
|
|
|
if (g_simple_async_result_propagate_error (simple, error))
|
2013-07-30 16:31:31 -04:00
|
|
|
return NM_CONNECTIVITY_UNKNOWN;
|
|
|
|
|
return (NMConnectivityState) g_simple_async_result_get_op_res_gssize (simple);
|
2013-07-31 09:14:39 -04:00
|
|
|
}
|
|
|
|
|
|
2016-10-02 18:22:50 +02:00
|
|
|
/*****************************************************************************/
|
2013-07-31 09:14:39 -04:00
|
|
|
|
2016-09-29 13:49:01 +02:00
|
|
|
static void
|
|
|
|
|
get_property (GObject *object, guint property_id,
|
|
|
|
|
GValue *value, GParamSpec *pspec)
|
2011-10-21 21:21:30 +02:00
|
|
|
{
|
2016-09-29 13:49:01 +02:00
|
|
|
NMConnectivity *self = NM_CONNECTIVITY (object);
|
|
|
|
|
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
|
|
|
|
|
|
|
|
|
|
switch (property_id) {
|
|
|
|
|
case PROP_URI:
|
|
|
|
|
g_value_set_string (value, priv->uri);
|
|
|
|
|
break;
|
|
|
|
|
case PROP_INTERVAL:
|
|
|
|
|
g_value_set_uint (value, priv->interval);
|
|
|
|
|
break;
|
|
|
|
|
case PROP_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);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2011-12-13 17:35:36 -06:00
|
|
|
}
|
2011-10-21 21:21:30 +02:00
|
|
|
|
|
|
|
|
static void
|
2011-12-05 14:48:47 -06:00
|
|
|
set_property (GObject *object, guint property_id,
|
|
|
|
|
const GValue *value, GParamSpec *pspec)
|
2011-10-21 21:21:30 +02:00
|
|
|
{
|
|
|
|
|
NMConnectivity *self = NM_CONNECTIVITY (object);
|
|
|
|
|
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
|
2014-07-14 13:40:30 +02:00
|
|
|
const char *uri, *response;
|
|
|
|
|
guint interval;
|
2015-07-16 16:58:14 +02:00
|
|
|
gboolean changed;
|
2011-10-21 21:21:30 +02:00
|
|
|
|
2011-12-05 12:29:55 -06:00
|
|
|
switch (property_id) {
|
2011-12-13 17:35:36 -06:00
|
|
|
case PROP_URI:
|
2014-07-14 13:40:30 +02:00
|
|
|
uri = g_value_get_string (value);
|
|
|
|
|
if (uri && !*uri)
|
|
|
|
|
uri = NULL;
|
2015-07-16 16:58:14 +02:00
|
|
|
changed = g_strcmp0 (uri, priv->uri) != 0;
|
2013-07-29 16:36:00 -04:00
|
|
|
#if WITH_CONCHECK
|
2014-07-14 13:40:30 +02:00
|
|
|
if (uri) {
|
2016-04-04 18:23:13 +02:00
|
|
|
char *scheme = g_uri_parse_scheme (uri);
|
2013-07-29 09:42:44 -04:00
|
|
|
|
2016-04-04 18:23:13 +02:00
|
|
|
if (!scheme) {
|
|
|
|
|
_LOGE ("invalid URI '%s' for connectivity check.", uri);
|
2014-07-14 13:40:30 +02:00
|
|
|
uri = NULL;
|
2016-04-04 18:23:13 +02:00
|
|
|
} else if (strcasecmp (scheme, "https") == 0) {
|
2015-07-16 16:58:14 +02:00
|
|
|
_LOGW ("use of HTTPS for connectivity checking is not reliable and is discouraged (URI: %s)", uri);
|
2016-04-04 18:23:13 +02:00
|
|
|
} 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);
|
2013-07-29 09:42:44 -04:00
|
|
|
}
|
2013-07-29 16:36:00 -04:00
|
|
|
#endif
|
2015-07-16 16:58:14 +02:00
|
|
|
if (changed) {
|
2014-07-14 13:40:30 +02:00
|
|
|
g_free (priv->uri);
|
|
|
|
|
priv->uri = g_strdup (uri);
|
|
|
|
|
_reschedule_periodic_checks (self, TRUE);
|
|
|
|
|
}
|
2011-10-21 21:21:30 +02:00
|
|
|
break;
|
2011-12-13 17:35:36 -06:00
|
|
|
case PROP_INTERVAL:
|
2014-07-14 13:40:30 +02:00
|
|
|
interval = g_value_get_uint (value);
|
|
|
|
|
if (priv->interval != interval) {
|
|
|
|
|
priv->interval = interval;
|
|
|
|
|
_reschedule_periodic_checks (self, TRUE);
|
|
|
|
|
}
|
2011-10-21 21:21:30 +02:00
|
|
|
break;
|
2011-12-13 17:35:36 -06:00
|
|
|
case PROP_RESPONSE:
|
2014-07-14 13:40:30 +02:00
|
|
|
response = g_value_get_string (value);
|
|
|
|
|
if (g_strcmp0 (response, priv->response) != 0) {
|
2015-06-06 21:29:01 +02:00
|
|
|
/* a response %NULL means, NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE. Any other response
|
2014-07-14 13:40:30 +02:00
|
|
|
* (including "") is accepted. */
|
|
|
|
|
g_free (priv->response);
|
|
|
|
|
priv->response = g_strdup (response);
|
|
|
|
|
_reschedule_periodic_checks (self, TRUE);
|
|
|
|
|
}
|
2011-10-21 21:21:30 +02:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-29 13:49:01 +02:00
|
|
|
/*****************************************************************************/
|
2011-10-21 21:21:30 +02:00
|
|
|
|
2016-04-04 18:23:13 +02:00
|
|
|
|
2011-10-21 21:21:30 +02:00
|
|
|
static void
|
|
|
|
|
nm_connectivity_init (NMConnectivity *self)
|
|
|
|
|
{
|
|
|
|
|
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
|
2014-07-14 13:40:30 +02:00
|
|
|
#if WITH_CONCHECK
|
2016-04-04 18:23:13 +02:00
|
|
|
CURLcode retv;
|
2013-07-29 16:36:00 -04:00
|
|
|
#endif
|
2016-04-04 18:23:13 +02:00
|
|
|
|
2014-07-14 13:40:30 +02:00
|
|
|
priv->state = NM_CONNECTIVITY_NONE;
|
2016-04-04 18:23:13 +02:00
|
|
|
#if WITH_CONCHECK
|
|
|
|
|
retv = curl_global_init (CURL_GLOBAL_ALL);
|
|
|
|
|
if (retv == CURLE_OK)
|
|
|
|
|
priv->curl_mhandle = curl_multi_init ();
|
|
|
|
|
|
|
|
|
|
if (priv->curl_mhandle == NULL) {
|
|
|
|
|
_LOGE ("Unable to init cURL, connectivity check will not work");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2017-03-22 18:48:58 +00:00
|
|
|
curl_multi_setopt (priv->curl_mhandle, CURLOPT_VERBOSE, 1);
|
2016-04-04 18:23:13 +02:00
|
|
|
#endif
|
2011-10-21 21:21:30 +02:00
|
|
|
}
|
|
|
|
|
|
2016-09-29 13:49:01 +02:00
|
|
|
NMConnectivity *
|
|
|
|
|
nm_connectivity_new (const char *uri,
|
|
|
|
|
guint interval,
|
|
|
|
|
const char *response)
|
|
|
|
|
{
|
|
|
|
|
return g_object_new (NM_TYPE_CONNECTIVITY,
|
|
|
|
|
NM_CONNECTIVITY_URI, uri,
|
|
|
|
|
NM_CONNECTIVITY_INTERVAL, interval,
|
|
|
|
|
NM_CONNECTIVITY_RESPONSE, response,
|
|
|
|
|
NULL);
|
|
|
|
|
}
|
2011-10-21 21:21:30 +02:00
|
|
|
|
|
|
|
|
static void
|
2011-12-05 14:48:47 -06:00
|
|
|
dispose (GObject *object)
|
2011-10-21 21:21:30 +02:00
|
|
|
{
|
2011-12-13 17:35:36 -06:00
|
|
|
NMConnectivity *self = NM_CONNECTIVITY (object);
|
|
|
|
|
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
|
2011-10-21 21:21:30 +02:00
|
|
|
|
2014-07-09 22:22:48 +02:00
|
|
|
g_clear_pointer (&priv->uri, g_free);
|
|
|
|
|
g_clear_pointer (&priv->response, g_free);
|
2013-07-29 16:36:00 -04:00
|
|
|
|
|
|
|
|
#if WITH_CONCHECK
|
2016-04-04 18:23:13 +02:00
|
|
|
curl_multi_cleanup (priv->curl_mhandle);
|
|
|
|
|
curl_global_cleanup ();
|
2011-10-21 21:21:30 +02:00
|
|
|
|
2016-02-01 13:15:40 +01:00
|
|
|
nm_clear_g_source (&priv->check_id);
|
2013-07-29 16:36:00 -04:00
|
|
|
#endif
|
2016-02-01 13:15:40 +01:00
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (nm_connectivity_parent_class)->dispose (object);
|
2011-10-21 21:21:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nm_connectivity_class_init (NMConnectivityClass *klass)
|
|
|
|
|
{
|
|
|
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
|
|
2011-12-05 14:48:47 -06:00
|
|
|
object_class->set_property = set_property;
|
|
|
|
|
object_class->get_property = get_property;
|
|
|
|
|
object_class->dispose = dispose;
|
2011-10-21 21:21:30 +02:00
|
|
|
|
2016-09-29 13:49:01 +02:00
|
|
|
obj_properties[PROP_URI] =
|
2014-07-14 12:52:08 +02:00
|
|
|
g_param_spec_string (NM_CONNECTIVITY_URI, "", "",
|
|
|
|
|
NULL,
|
|
|
|
|
G_PARAM_READWRITE |
|
2015-01-19 17:41:22 +01:00
|
|
|
G_PARAM_CONSTRUCT |
|
2016-09-29 13:49:01 +02:00
|
|
|
G_PARAM_STATIC_STRINGS);
|
2011-12-13 17:35:36 -06:00
|
|
|
|
2016-09-29 13:49:01 +02:00
|
|
|
obj_properties[PROP_INTERVAL] =
|
2014-07-14 12:52:08 +02:00
|
|
|
g_param_spec_uint (NM_CONNECTIVITY_INTERVAL, "", "",
|
2015-02-19 14:24:37 +01:00
|
|
|
0, G_MAXUINT, NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL,
|
2014-07-14 12:52:08 +02:00
|
|
|
G_PARAM_READWRITE |
|
|
|
|
|
G_PARAM_CONSTRUCT |
|
2016-09-29 13:49:01 +02:00
|
|
|
G_PARAM_STATIC_STRINGS);
|
2011-12-13 17:35:36 -06:00
|
|
|
|
2016-09-29 13:49:01 +02:00
|
|
|
obj_properties[PROP_RESPONSE] =
|
2014-07-14 12:52:08 +02:00
|
|
|
g_param_spec_string (NM_CONNECTIVITY_RESPONSE, "", "",
|
2015-06-06 21:29:01 +02:00
|
|
|
NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE,
|
2014-07-14 12:52:08 +02:00
|
|
|
G_PARAM_READWRITE |
|
|
|
|
|
G_PARAM_CONSTRUCT |
|
2016-09-29 13:49:01 +02:00
|
|
|
G_PARAM_STATIC_STRINGS);
|
2011-12-13 17:35:36 -06:00
|
|
|
|
2016-09-29 13:49:01 +02:00
|
|
|
obj_properties[PROP_STATE] =
|
2014-07-14 12:52:08 +02:00
|
|
|
g_param_spec_uint (NM_CONNECTIVITY_STATE, "", "",
|
|
|
|
|
NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
|
|
|
|
|
G_PARAM_READABLE |
|
2016-09-29 13:49:01 +02:00
|
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
|
|
|
|
|
|
g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
|
2011-10-21 21:21:30 +02:00
|
|
|
}
|