NetworkManager/src/nm-pacrunner-manager.c
Thomas Haller 6cfd927962 proxy: fix passing cancellable to async D-Bus operations
We must not cancel pacrunner_cancellable when the D-Bus proxy is
created. Instead, keep it around and use it later for the asynchronous
D-Bus operations.

This doesn't really matter at the moment, because the pacrunner manager
is only destroyed when NetworkManager is about to terminated. That is
the only time when we actually cancel the asynchronous request. Also,
at that time we no longer iterate the mainloop, so the pending requests
are never completed anyway.

(cherry picked from commit a08540d967)
2017-05-11 18:55:23 +02:00

569 lines
16 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager
*
* 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.
*
* (C) Copyright 2016 Atul Anand <atulhjp@gmail.com>.
*/
#include "nm-default.h"
#include "nm-pacrunner-manager.h"
#include "nm-utils.h"
#include "platform/nm-platform.h"
#include "nm-proxy-config.h"
#include "nm-ip4-config.h"
#include "nm-ip6-config.h"
static void pacrunner_remove_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data);
#define PACRUNNER_DBUS_SERVICE "org.pacrunner"
#define PACRUNNER_DBUS_INTERFACE "org.pacrunner.Manager"
#define PACRUNNER_DBUS_PATH "/org/pacrunner/manager"
/*****************************************************************************/
struct _NMPacrunnerCallId {
NMPacrunnerManager *manager;
GVariant *args;
char *path;
guint refcount;
bool removed;
};
typedef struct _NMPacrunnerCallId Config;
typedef struct {
char *iface;
GDBusProxy *pacrunner;
GCancellable *pacrunner_cancellable;
GList *configs;
} NMPacrunnerManagerPrivate;
struct _NMPacrunnerManager {
GObject parent;
NMPacrunnerManagerPrivate _priv;
};
struct _NMPacrunnerManagerClass {
GObjectClass parent;
};
G_DEFINE_TYPE (NMPacrunnerManager, nm_pacrunner_manager, G_TYPE_OBJECT)
#define NM_PACRUNNER_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMPacrunnerManager, NM_IS_PACRUNNER_MANAGER)
/*****************************************************************************/
NM_DEFINE_SINGLETON_GETTER (NMPacrunnerManager, nm_pacrunner_manager_get, NM_TYPE_PACRUNNER_MANAGER);
/*****************************************************************************/
#define _NMLOG_DOMAIN LOGD_PROXY
#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "pacrunner", __VA_ARGS__)
#define _NMLOG2_PREFIX_NAME "pacrunner"
#define _NMLOG2(level, config, ...) \
G_STMT_START { \
nm_log ((level), _NMLOG_DOMAIN, NULL, NULL, \
"%s%p]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
"pacrunner: call[", \
(config) \
_NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} G_STMT_END
/*****************************************************************************/
static Config *
config_new (NMPacrunnerManager *manager, GVariant *args)
{
Config *config;
config = g_slice_new0 (Config);
config->manager = manager;
config->args = g_variant_ref_sink (args);
config->refcount = 1;
return config;
}
static void
config_ref (Config *config)
{
g_assert (config);
g_assert (config->refcount > 0);
config->refcount++;
}
static void
config_unref (Config *config)
{
g_assert (config);
g_assert (config->refcount > 0);
if (config->refcount == 1) {
g_variant_unref (config->args);
g_free (config->path);
g_slice_free (Config, config);
} else
config->refcount--;
}
static void
add_proxy_config (GVariantBuilder *proxy_data, const NMProxyConfig *proxy_config)
{
const char *pac_url, *pac_script;
NMProxyConfigMethod method;
method = nm_proxy_config_get_method (proxy_config);
if (method == NM_PROXY_CONFIG_METHOD_AUTO) {
pac_url = nm_proxy_config_get_pac_url (proxy_config);
if (pac_url) {
g_variant_builder_add (proxy_data, "{sv}",
"URL",
g_variant_new_string (pac_url));
}
pac_script = nm_proxy_config_get_pac_script (proxy_config);
if (pac_script) {
g_variant_builder_add (proxy_data, "{sv}",
"Script",
g_variant_new_string (pac_script));
}
}
g_variant_builder_add (proxy_data, "{sv}",
"BrowserOnly",
g_variant_new_boolean (nm_proxy_config_get_browser_only (proxy_config)));
}
static void
get_ip4_domains (GPtrArray *domains, NMIP4Config *ip4)
{
char *cidr;
int i;
/* Extract searches */
for (i = 0; i < nm_ip4_config_get_num_searches (ip4); i++)
g_ptr_array_add (domains, g_strdup (nm_ip4_config_get_search (ip4, i)));
/* Extract domains */
for (i = 0; i < nm_ip4_config_get_num_domains (ip4); i++)
g_ptr_array_add (domains, g_strdup (nm_ip4_config_get_domain (ip4, i)));
/* Add addresses and routes in CIDR form */
for (i = 0; i < nm_ip4_config_get_num_addresses (ip4); i++) {
const NMPlatformIP4Address *address = nm_ip4_config_get_address (ip4, i);
cidr = g_strdup_printf ("%s/%u",
nm_utils_inet4_ntop (address->address, NULL),
address->plen);
g_ptr_array_add (domains, cidr);
}
for (i = 0; i < nm_ip4_config_get_num_routes (ip4); i++) {
const NMPlatformIP4Route *routes = nm_ip4_config_get_route (ip4, i);
cidr = g_strdup_printf ("%s/%u",
nm_utils_inet4_ntop (routes->network, NULL),
routes->plen);
g_ptr_array_add (domains, cidr);
}
}
static void
get_ip6_domains (GPtrArray *domains, NMIP6Config *ip6)
{
char *cidr;
int i;
/* Extract searches */
for (i = 0; i < nm_ip6_config_get_num_searches (ip6); i++)
g_ptr_array_add (domains, g_strdup (nm_ip6_config_get_search (ip6, i)));
/* Extract domains */
for (i = 0; i < nm_ip6_config_get_num_domains (ip6); i++)
g_ptr_array_add (domains, g_strdup (nm_ip6_config_get_domain (ip6, i)));
/* Add addresses and routes in CIDR form */
for (i = 0; i < nm_ip6_config_get_num_addresses (ip6); i++) {
const NMPlatformIP6Address *address = nm_ip6_config_get_address (ip6, i);
cidr = g_strdup_printf ("%s/%u",
nm_utils_inet6_ntop (&address->address, NULL),
address->plen);
g_ptr_array_add (domains, cidr);
}
for (i = 0; i < nm_ip6_config_get_num_routes (ip6); i++) {
const NMPlatformIP6Route *routes = nm_ip6_config_get_route (ip6, i);
cidr = g_strdup_printf ("%s/%u",
nm_utils_inet6_ntop (&routes->network, NULL),
routes->plen);
g_ptr_array_add (domains, cidr);
}
}
static void
pacrunner_send_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
{
Config *config = user_data;
NMPacrunnerManager *self;
NMPacrunnerManagerPrivate *priv;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *variant = NULL;
const char *path = NULL;
g_return_if_fail (!config->path);
variant = g_dbus_proxy_call_finish (proxy, res, &error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
config_unref (config);
return;
}
self = NM_PACRUNNER_MANAGER (config->manager);
priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
if (!variant)
_LOG2D (config, "sending failed: %s", error->message);
else {
g_variant_get (variant, "(&o)", &path);
config->path = g_strdup (path);
_LOG2D (config, "sent");
if (config->removed) {
config_ref (config);
g_dbus_proxy_call (priv->pacrunner,
"DestroyProxyConfiguration",
g_variant_new ("(o)", config->path),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1,
priv->pacrunner_cancellable,
(GAsyncReadyCallback) pacrunner_remove_done,
config);
}
}
config_unref (config);
}
static void
pacrunner_send_config (NMPacrunnerManager *self, Config *config)
{
NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
if (priv->pacrunner) {
_LOG2T (config, "sending...");
config_ref (config);
g_clear_pointer (&config->path, g_free);
g_dbus_proxy_call (priv->pacrunner,
"CreateProxyConfiguration",
config->args,
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1,
priv->pacrunner_cancellable,
(GAsyncReadyCallback) pacrunner_send_done,
config);
}
}
static void
name_owner_changed (NMPacrunnerManager *self)
{
NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
gs_free char *owner = NULL;
GList *iter = NULL;
owner = g_dbus_proxy_get_name_owner (priv->pacrunner);
if (owner) {
_LOGD ("name owner appeared (%s)", owner);
for (iter = g_list_first (priv->configs); iter; iter = g_list_next (iter))
pacrunner_send_config (self, iter->data);
} else {
_LOGD ("name owner disappeared");
}
}
static void
name_owner_changed_cb (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
name_owner_changed (user_data);
}
static void
pacrunner_proxy_cb (GObject *source, GAsyncResult *res, gpointer user_data)
{
NMPacrunnerManager *self = user_data;
NMPacrunnerManagerPrivate *priv;
GError *error = NULL;
GDBusProxy *proxy;
proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
if (!proxy) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
_LOGW ("failed to connect to pacrunner via DBus: %s", error->message);
g_error_free (error);
return;
}
priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
priv->pacrunner = proxy;
g_signal_connect (priv->pacrunner, "notify::g-name-owner",
G_CALLBACK (name_owner_changed_cb), self);
name_owner_changed (self);
}
/**
* nm_pacrunner_manager_send:
* @self: the #NMPacrunnerManager
* @iface: the iface for the connection or %NULL
* @proxy_config: proxy config of the connection
* @ip4_config: IP4 config of the connection to extract domain info from
* @ip6_config: IP6 config of the connection to extract domain info from
*
* Returns: a #NMPacrunnerCallId call id. The function cannot
* fail and always returns a non NULL pointer. The call-id may
* be used to remove the configuration later via nm_pacrunner_manager_remove().
* Note that the call-id does not keep the @self instance alive.
* If you plan to remove the configuration later, you must keep
* the instance alive long enough. You can remove the configuration
* at most once using this call call-id.
*/
NMPacrunnerCallId *
nm_pacrunner_manager_send (NMPacrunnerManager *self,
const char *iface,
NMProxyConfig *proxy_config,
NMIP4Config *ip4_config,
NMIP6Config *ip6_config)
{
char **strv = NULL;
NMProxyConfigMethod method;
NMPacrunnerManagerPrivate *priv;
GVariantBuilder proxy_data;
GPtrArray *domains;
Config *config;
g_return_val_if_fail (NM_IS_PACRUNNER_MANAGER (self), NULL);
g_return_val_if_fail (proxy_config, NULL);
priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
g_free (priv->iface);
priv->iface = g_strdup (iface);
g_variant_builder_init (&proxy_data, G_VARIANT_TYPE_VARDICT);
if (iface) {
g_variant_builder_add (&proxy_data, "{sv}",
"Interface",
g_variant_new_string (iface));
}
method = nm_proxy_config_get_method (proxy_config);
switch (method) {
case NM_PROXY_CONFIG_METHOD_AUTO:
g_variant_builder_add (&proxy_data, "{sv}",
"Method",
g_variant_new_string ("auto"));
break;
case NM_PROXY_CONFIG_METHOD_NONE:
g_variant_builder_add (&proxy_data, "{sv}",
"Method",
g_variant_new_string ("direct"));
}
/* Extract stuff from configs */
add_proxy_config (&proxy_data, proxy_config);
if (ip4_config || ip6_config) {
domains = g_ptr_array_new_with_free_func (g_free);
if (ip4_config)
get_ip4_domains (domains, ip4_config);
if (ip6_config)
get_ip6_domains (domains, ip6_config);
g_ptr_array_add (domains, NULL);
strv = (char **) g_ptr_array_free (domains, (domains->len == 1));
if (strv) {
g_variant_builder_add (&proxy_data, "{sv}",
"Domains",
g_variant_new_strv ((const char *const *) strv, -1));
g_strfreev (strv);
}
}
config = config_new (self, g_variant_new ("(a{sv})", &proxy_data));
priv->configs = g_list_append (priv->configs, config);
{
gs_free char *args_str = NULL;
_LOG2D (config, "send: new config %s",
(args_str = g_variant_print (config->args, FALSE)));
}
/* Send if pacrunner is available on bus, otherwise
* config has already been appended above to be
* sent when pacrunner appears.
*/
pacrunner_send_config (self, config);
return config;
}
static void
pacrunner_remove_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
{
Config *config = user_data;
NMPacrunnerManager *self;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *ret = NULL;
ret = g_dbus_proxy_call_finish (proxy, res, &error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
config_unref (config);
return;
}
self = NM_PACRUNNER_MANAGER (config->manager);
if (!ret)
_LOG2D (config, "remove failed: %s", error->message);
else
_LOG2D (config, "removed");
config_unref (config);
}
/**
* nm_pacrunner_manager_remove:
* @self: the #NMPacrunnerManager
* @call_id: the call-id obtained from nm_pacrunner_manager_send()
*/
void
nm_pacrunner_manager_remove (NMPacrunnerManager *self, NMPacrunnerCallId *call_id)
{
NMPacrunnerManagerPrivate *priv;
Config *config;
GList *list;
g_return_if_fail (NM_IS_PACRUNNER_MANAGER (self));
g_return_if_fail (call_id);
config = call_id;
priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
_LOG2T (config, "removing...");
list = g_list_find (priv->configs, config);
if (!list)
g_return_if_reached ();
if (priv->pacrunner) {
if (!config->path) {
/* send() failed or is still pending. Mark the item as
* removed, so that we ask pacrunner to drop it when the
* send() completes.
*/
config->removed = TRUE;
config_unref (config);
} else {
g_dbus_proxy_call (priv->pacrunner,
"DestroyProxyConfiguration",
g_variant_new ("(o)", config->path),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1,
priv->pacrunner_cancellable,
(GAsyncReadyCallback) pacrunner_remove_done,
config);
}
} else
config_unref (config);
priv->configs = g_list_delete_link (priv->configs, list);
}
gboolean
nm_pacrunner_manager_remove_clear (NMPacrunnerManager *self,
NMPacrunnerCallId **p_call_id)
{
g_return_val_if_fail (p_call_id, FALSE);
/* if we have no call-id, allow for %NULL */
g_return_val_if_fail ((!self && !*p_call_id) || NM_IS_PACRUNNER_MANAGER (self), FALSE);
if (!*p_call_id)
return FALSE;
nm_pacrunner_manager_remove (self,
g_steal_pointer (p_call_id));
return TRUE;
}
/*****************************************************************************/
static void
nm_pacrunner_manager_init (NMPacrunnerManager *self)
{
NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
priv->pacrunner_cancellable = g_cancellable_new ();
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
PACRUNNER_DBUS_SERVICE,
PACRUNNER_DBUS_PATH,
PACRUNNER_DBUS_INTERFACE,
priv->pacrunner_cancellable,
(GAsyncReadyCallback) pacrunner_proxy_cb,
self);
}
static void
dispose (GObject *object)
{
NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE ((NMPacrunnerManager *) object);
g_clear_pointer (&priv->iface, g_free);
nm_clear_g_cancellable (&priv->pacrunner_cancellable);
g_clear_object (&priv->pacrunner);
g_list_free_full (priv->configs, (GDestroyNotify) config_unref);
priv->configs = NULL;
G_OBJECT_CLASS (nm_pacrunner_manager_parent_class)->dispose (object);
}
static void
nm_pacrunner_manager_class_init (NMPacrunnerManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = dispose;
}