diff options
Diffstat (limited to 'src/core/nm-pacrunner-manager.c')
| -rw-r--r-- | src/core/nm-pacrunner-manager.c | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/src/core/nm-pacrunner-manager.c b/src/core/nm-pacrunner-manager.c new file mode 100644 index 00000000..b58aef06 --- /dev/null +++ b/src/core/nm-pacrunner-manager.c @@ -0,0 +1,591 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Atul Anand <atulhjp@gmail.com>. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-pacrunner-manager.h" + +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "platform/nm-platform.h" +#include "nm-dbus-manager.h" +#include "nm-proxy-config.h" +#include "nm-ip4-config.h" +#include "nm-ip6-config.h" +#include "c-list/src/c-list.h" +#include "nm-glib-aux/nm-dbus-aux.h" + +#define PACRUNNER_DBUS_SERVICE "org.pacrunner" +#define PACRUNNER_DBUS_INTERFACE "org.pacrunner.Manager" +#define PACRUNNER_DBUS_PATH "/org/pacrunner/manager" + +/*****************************************************************************/ + +struct _NMPacrunnerConfId { + CList conf_id_lst; + + NMPacrunnerManager *self; + + GVariant *parameters; + + char * path; + guint64 log_id; + guint refcount; +}; + +typedef struct { + GDBusConnection *dbus_connection; + GCancellable * cancellable; + CList conf_id_lst_head; + guint64 log_id_counter; + guint name_owner_changed_id; + bool dbus_initied : 1; + bool has_name_owner : 1; + bool try_start_blocked : 1; +} 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, conf_id, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + NULL, \ + NULL, \ + "%s%" G_GUINT64_FORMAT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG2_PREFIX_NAME ": call[", \ + (conf_id)->log_id _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void _call_destroy_proxy_configuration(NMPacrunnerManager *self, + NMPacrunnerConfId * conf_id, + const char * path, + gboolean verbose_log); + +/*****************************************************************************/ + +static NMPacrunnerConfId * +conf_id_ref(NMPacrunnerConfId *conf_id) +{ + nm_assert(conf_id); + nm_assert(conf_id->refcount > 0); + + conf_id->refcount++; + return conf_id; +} + +static void +conf_id_unref(NMPacrunnerConfId *conf_id) +{ + nm_assert(conf_id); + nm_assert(conf_id->refcount > 0); + + if (conf_id->refcount == 1) { + g_variant_unref(conf_id->parameters); + g_free(conf_id->path); + c_list_unlink_stale(&conf_id->conf_id_lst); + g_object_unref(conf_id->self); + g_slice_free(NMPacrunnerConfId, conf_id); + } else + conf_id->refcount--; +} + +NM_AUTO_DEFINE_FCN0(NMPacrunnerConfId *, _nm_auto_unref_conf_id, conf_id_unref); +#define nm_auto_unref_conf_id nm_auto(_nm_auto_unref_conf_id) + +/*****************************************************************************/ + +static void +get_ip_domains(GPtrArray *domains, NMIPConfig *ip_config) +{ + NMDedupMultiIter ipconf_iter; + char * cidr; + guint i, num; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + int addr_family; + const NMPlatformIPAddress *address; + const NMPlatformIPRoute * routes; + + if (!ip_config) + return; + + addr_family = nm_ip_config_get_addr_family(ip_config); + + num = nm_ip_config_get_num_searches(ip_config); + for (i = 0; i < num; i++) + g_ptr_array_add(domains, g_strdup(nm_ip_config_get_search(ip_config, i))); + + num = nm_ip_config_get_num_domains(ip_config); + for (i = 0; i < num; i++) + g_ptr_array_add(domains, g_strdup(nm_ip_config_get_domain(ip_config, i))); + + nm_ip_config_iter_ip_address_for_each (&ipconf_iter, ip_config, &address) { + cidr = g_strdup_printf("%s/%u", + nm_utils_inet_ntop(addr_family, address->address_ptr, sbuf), + address->plen); + g_ptr_array_add(domains, cidr); + } + + nm_ip_config_iter_ip_route_for_each (&ipconf_iter, ip_config, &routes) { + if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(routes)) + continue; + cidr = g_strdup_printf("%s/%u", + nm_utils_inet_ntop(addr_family, routes->network_ptr, sbuf), + routes->plen); + g_ptr_array_add(domains, cidr); + } +} + +static GVariant * +_make_request_create_proxy_configuration(NMProxyConfig *proxy_config, + const char * iface, + NMIP4Config * ip4_config, + NMIP6Config * ip6_config) +{ + GVariantBuilder builder; + NMProxyConfigMethod method; + const char * pac_url; + const char * pac_script; + + nm_assert(NM_IS_PROXY_CONFIG(proxy_config)); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + if (iface) { + g_variant_builder_add(&builder, "{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(&builder, "{sv}", "Method", g_variant_new_string("auto")); + + pac_url = nm_proxy_config_get_pac_url(proxy_config); + if (pac_url) { + g_variant_builder_add(&builder, "{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(&builder, "{sv}", "Script", g_variant_new_string(pac_script)); + } + break; + case NM_PROXY_CONFIG_METHOD_NONE: + g_variant_builder_add(&builder, "{sv}", "Method", g_variant_new_string("direct")); + break; + } + + g_variant_builder_add(&builder, + "{sv}", + "BrowserOnly", + g_variant_new_boolean(nm_proxy_config_get_browser_only(proxy_config))); + + if (ip4_config || ip6_config) { + gs_unref_ptrarray GPtrArray *domains = NULL; + + domains = g_ptr_array_new_with_free_func(g_free); + + get_ip_domains(domains, NM_IP_CONFIG_CAST(ip4_config)); + get_ip_domains(domains, NM_IP_CONFIG_CAST(ip6_config)); + + if (domains->len > 0) { + g_variant_builder_add( + &builder, + "{sv}", + "Domains", + g_variant_new_strv((const char *const *) domains->pdata, domains->len)); + } + } + + return g_variant_new("(a{sv})", &builder); +} + +/*****************************************************************************/ + +static void +_call_destroy_proxy_configuration_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + nm_auto_unref_conf_id NMPacrunnerConfId *conf_id = user_data; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *ret = NULL; + + ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); + if (!ret) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + _LOG2T(conf_id, "destroy proxy configuration: failed with %s", error->message); + else + _LOG2T(conf_id, "destroy proxy configuration: cancelled"); + return; + } + _LOG2T(conf_id, "destroy proxy configuration: success"); +} + +static void +_call_create_proxy_configuration_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + nm_auto_unref_conf_id NMPacrunnerConfId *conf_id = user_data; + NMPacrunnerManager * self = NM_PACRUNNER_MANAGER(conf_id->self); + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *variant = NULL; + const char * path = NULL; + + nm_assert(!conf_id->path); + + variant = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); + + if (!variant) { + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + _LOG2T(conf_id, "create proxy configuration failed: %s", error->message); + else + _LOG2T(conf_id, "create proxy configuration cancelled"); + return; + } + + g_variant_get(variant, "(&o)", &path); + + if (c_list_is_empty(&conf_id->conf_id_lst)) { + _LOG2T(conf_id, + "create proxy configuration succeeded (%s), but destroy it right away", + path); + _call_destroy_proxy_configuration(self, conf_id, path, FALSE); + } else { + _LOG2T(conf_id, "create proxy configuration succeeded (%s)", path); + conf_id->path = g_strdup(path); + } +} + +static void +_call_destroy_proxy_configuration(NMPacrunnerManager *self, + NMPacrunnerConfId * conf_id, + const char * path, + gboolean verbose_log) +{ + NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + + if (verbose_log) + _LOG2T(conf_id, "destroy proxy configuration %s...", path); + + g_dbus_connection_call(priv->dbus_connection, + PACRUNNER_DBUS_SERVICE, + PACRUNNER_DBUS_PATH, + PACRUNNER_DBUS_INTERFACE, + "DestroyProxyConfiguration", + g_variant_new("(o)", path), + G_VARIANT_TYPE("()"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + NM_SHUTDOWN_TIMEOUT_MS, + priv->cancellable, + _call_destroy_proxy_configuration_cb, + conf_id_ref(conf_id)); +} + +static void +_call_create_proxy_configuration(NMPacrunnerManager *self, + NMPacrunnerConfId * conf_id, + gboolean verbose_log) +{ + NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + + if (verbose_log) + _LOG2T(conf_id, "create proxy configuration..."); + + g_dbus_connection_call(priv->dbus_connection, + PACRUNNER_DBUS_SERVICE, + PACRUNNER_DBUS_PATH, + PACRUNNER_DBUS_INTERFACE, + "CreateProxyConfiguration", + conf_id->parameters, + G_VARIANT_TYPE("(o)"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + NM_SHUTDOWN_TIMEOUT_MS, + priv->cancellable, + _call_create_proxy_configuration_cb, + conf_id_ref(conf_id)); +} + +static gboolean +_try_start_service_by_name(NMPacrunnerManager *self) +{ + NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + + if (priv->try_start_blocked || !priv->dbus_initied) + return FALSE; + + _LOGD("try D-Bus activating pacrunner..."); + priv->try_start_blocked = TRUE; + nm_dbus_connection_call_start_service_by_name(priv->dbus_connection, + PACRUNNER_DBUS_SERVICE, + -1, + NULL, + NULL, + NULL); + return TRUE; +} + +/*****************************************************************************/ + +/** + * nm_pacrunner_manager_add: + * @self: the #NMPacrunnerManager + * @proxy_config: proxy config of the connection + * @iface: the iface for the connection or %NULL + * @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 #NMPacrunnerConfId id. The function cannot + * fail and always returns a non NULL pointer. The conf-id may + * be used to remove the configuration later via nm_pacrunner_manager_remove(). + * Note that the conf-id keeps the @self instance alive. + */ +NMPacrunnerConfId * +nm_pacrunner_manager_add(NMPacrunnerManager *self, + NMProxyConfig * proxy_config, + const char * iface, + NMIP4Config * ip4_config, + NMIP6Config * ip6_config) +{ + NMPacrunnerManagerPrivate *priv; + NMPacrunnerConfId * conf_id; + gs_free char * log_msg = NULL; + + 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); + + conf_id = g_slice_new(NMPacrunnerConfId); + *conf_id = (NMPacrunnerConfId){ + .log_id = ++priv->log_id_counter, + .refcount = 1, + .self = g_object_ref(self), + .parameters = g_variant_ref_sink( + _make_request_create_proxy_configuration(proxy_config, iface, ip4_config, ip6_config)), + }; + c_list_link_tail(&priv->conf_id_lst_head, &conf_id->conf_id_lst); + + if (!priv->has_name_owner) { + _LOG2T(conf_id, + "add config: %s (%s)", + (log_msg = g_variant_print(conf_id->parameters, FALSE)), + "pacrunner D-Bus service not running"); + _try_start_service_by_name(self); + } else { + _LOG2T(conf_id, + "add config: %s (%s)", + (log_msg = g_variant_print(conf_id->parameters, FALSE)), + "create proxy configuration"); + _call_create_proxy_configuration(self, conf_id, FALSE); + } + + return conf_id; +} + +/** + * nm_pacrunner_manager_remove: + * @conf_id: the conf id obtained from nm_pacrunner_manager_add() + */ +void +nm_pacrunner_manager_remove(NMPacrunnerConfId *conf_id) +{ + _nm_unused nm_auto_unref_conf_id NMPacrunnerConfId *conf_id_free = conf_id; + NMPacrunnerManager * self; + NMPacrunnerManagerPrivate * priv; + + g_return_if_fail(conf_id); + + self = conf_id->self; + + g_return_if_fail(NM_IS_PACRUNNER_MANAGER(self)); + + priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + + _LOG2T(conf_id, "removing..."); + + nm_assert(c_list_contains(&priv->conf_id_lst_head, &conf_id->conf_id_lst)); + + c_list_unlink(&conf_id->conf_id_lst); + + if (!conf_id->path) { + /* There is no ID to destroy the configuration. + * + * That can happen because: + * + * - pacrunner D-Bus service is not running (no name owner) and we didn't call CreateProxyConfiguration. + * - CreateProxyConfiguration failed. + * - CreateProxyConfiguration is in progress. + * + * In all cases there is nothing to do. Note that if CreateProxyConfiguration is in progress + * it has a reference on the conf-id and it will automatically destroy the configuration + * when it completes. + */ + return; + } + + _call_destroy_proxy_configuration(self, conf_id, conf_id->path, TRUE); +} + +gboolean +nm_pacrunner_manager_remove_clear(NMPacrunnerConfId **p_conf_id) +{ + g_return_val_if_fail(p_conf_id, FALSE); + + if (!*p_conf_id) + return FALSE; + nm_pacrunner_manager_remove(g_steal_pointer(p_conf_id)); + return TRUE; +} + +/*****************************************************************************/ + +static void +name_owner_changed(NMPacrunnerManager *self, const char *name_owner) +{ + NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + NMPacrunnerConfId * conf_id; + gboolean has_name_owner; + + has_name_owner = (name_owner && name_owner[0]); + + if (priv->dbus_initied && priv->has_name_owner == has_name_owner) + return; + + priv->has_name_owner = has_name_owner; + + nm_clear_g_cancellable(&priv->cancellable); + + if (has_name_owner) { + priv->dbus_initied = TRUE; + priv->try_start_blocked = FALSE; + _LOGD("pacrunner appeared on D-Bus (%s)", name_owner); + priv->cancellable = g_cancellable_new(); + c_list_for_each_entry (conf_id, &priv->conf_id_lst_head, conf_id_lst) + _call_create_proxy_configuration(self, conf_id, TRUE); + } else { + if (!priv->dbus_initied) { + priv->dbus_initied = TRUE; + nm_assert(!priv->try_start_blocked); + _LOGD("pacrunner not on D-Bus"); + } else + _LOGD("pacrunner disappeared from D-Bus"); + if (!c_list_is_empty(&priv->conf_id_lst_head)) { + c_list_for_each_entry (conf_id, &priv->conf_id_lst_head, conf_id_lst) + nm_clear_g_free(&conf_id->path); + _try_start_service_by_name(self); + } + } +} + +static void +name_owner_changed_cb(GDBusConnection *connection, + const char * sender_name, + const char * object_path, + const char * interface_name, + const char * signal_name, + GVariant * parameters, + gpointer user_data) +{ + const char *new_owner; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) + return; + + g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); + + name_owner_changed(user_data, new_owner); +} + +static void +get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) +{ + if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + name_owner_changed(user_data, name_owner); +} + +/*****************************************************************************/ + +static void +nm_pacrunner_manager_init(NMPacrunnerManager *self) +{ + NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(self); + + c_list_init(&priv->conf_id_lst_head); + + priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); + + if (!priv->dbus_connection) { + _LOGD("no D-Bus connection to talk to pacrunner"); + return; + } + + priv->name_owner_changed_id = + nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, + PACRUNNER_DBUS_SERVICE, + name_owner_changed_cb, + self, + NULL); + priv->cancellable = g_cancellable_new(); + + nm_dbus_connection_call_get_name_owner(priv->dbus_connection, + PACRUNNER_DBUS_SERVICE, + -1, + priv->cancellable, + get_name_owner_cb, + self); +} + +static void +dispose(GObject *object) +{ + NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE(object); + + nm_assert(c_list_is_empty(&priv->conf_id_lst_head)); + + /* we cancel all pending operations. Note that pacrunner automatically + * removes all configuration once NetworkManager disconnects from + * the bus -- which happens soon after we destroy the pacrunner manager. + */ + nm_clear_g_cancellable(&priv->cancellable); + + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); + g_clear_object(&priv->dbus_connection); + + 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; +} |