mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-27 18:40:09 +01:00
"libnm-core" implements common functionality for "NetworkManager" and
"libnm".
Note that clients like "nmcli" cannot access the internal API provided
by "libnm-core". So, if nmcli wants to do something that is also done by
"libnm-core", , "libnm", or "NetworkManager", the code would have to be
duplicated.
Instead, such code can be in "libnm-libnm-core-{intern|aux}.la".
Note that:
0) "libnm-libnm-core-intern.la" is used by libnm-core itsself.
On the other hand, "libnm-libnm-core-aux.la" is not used by
libnm-core, but provides utilities on top of it.
1) they both extend "libnm-core" with utlities that are not public
API of libnm itself. Maybe part of the code should one day become
public API of libnm. On the other hand, this is code for which
we may not want to commit to a stable interface or which we
don't want to provide as part of the API.
2) "libnm-libnm-core-intern.la" is statically linked by "libnm-core"
and thus directly available to "libnm" and "NetworkManager".
On the other hand, "libnm-libnm-core-aux.la" may be used by "libnm"
and "NetworkManager".
Both libraries may be statically linked by libnm clients (like
nmcli).
3) it must only use glib, libnm-glib-aux.la, and the public API
of libnm-core.
This is important: it must not use "libnm-core/nm-core-internal.h"
nor "libnm-core/nm-utils-private.h" so the static library is usable
by nmcli which couldn't access these.
Note that "shared/nm-meta-setting.c" is an entirely different case,
because it behaves differently depending on whether linking against
"libnm-core" or the client programs. As such, this file must be compiled
twice.
(cherry picked from commit af07ed01c0)
1011 lines
36 KiB
C
1011 lines
36 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) 2004 - 2017 Red Hat, Inc.
|
|
* Copyright (C) 2005 - 2008 Novell, Inc.
|
|
*/
|
|
|
|
#include "nm-default.h"
|
|
|
|
#include "nm-dispatcher.h"
|
|
|
|
#include "nm-libnm-core-aux/nm-dispatcher-api.h"
|
|
#include "NetworkManagerUtils.h"
|
|
#include "nm-utils.h"
|
|
#include "nm-connectivity.h"
|
|
#include "nm-act-request.h"
|
|
#include "devices/nm-device.h"
|
|
#include "nm-dhcp4-config.h"
|
|
#include "nm-dhcp6-config.h"
|
|
#include "nm-proxy-config.h"
|
|
#include "nm-ip4-config.h"
|
|
#include "nm-ip6-config.h"
|
|
#include "nm-manager.h"
|
|
#include "settings/nm-settings-connection.h"
|
|
#include "platform/nm-platform.h"
|
|
#include "nm-core-internal.h"
|
|
|
|
#define CALL_TIMEOUT (1000 * 60 * 10) /* 10 minutes for all scripts */
|
|
|
|
#define _NMLOG_DOMAIN LOGD_DISPATCH
|
|
#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "dispatcher", __VA_ARGS__)
|
|
|
|
static GDBusProxy *dispatcher_proxy;
|
|
static GHashTable *requests = NULL;
|
|
|
|
typedef struct {
|
|
GFileMonitor *monitor;
|
|
const char *const description;
|
|
const char *const dir;
|
|
const guint16 dir_len;
|
|
char has_scripts;
|
|
} Monitor;
|
|
|
|
enum {
|
|
MONITOR_INDEX_DEFAULT,
|
|
MONITOR_INDEX_PRE_UP,
|
|
MONITOR_INDEX_PRE_DOWN,
|
|
};
|
|
|
|
static Monitor monitors[3] = {
|
|
#define MONITORS_INIT_SET(INDEX, USE, SCRIPT_DIR) [INDEX] = { .dir_len = NM_STRLEN (SCRIPT_DIR), .dir = SCRIPT_DIR, .description = ("" USE), .has_scripts = TRUE }
|
|
MONITORS_INIT_SET (MONITOR_INDEX_DEFAULT, "default", NMD_SCRIPT_DIR_DEFAULT),
|
|
MONITORS_INIT_SET (MONITOR_INDEX_PRE_UP, "pre-up", NMD_SCRIPT_DIR_PRE_UP),
|
|
MONITORS_INIT_SET (MONITOR_INDEX_PRE_DOWN, "pre-down", NMD_SCRIPT_DIR_PRE_DOWN),
|
|
};
|
|
|
|
static const Monitor*
|
|
_get_monitor_by_action (NMDispatcherAction action)
|
|
{
|
|
switch (action) {
|
|
case NM_DISPATCHER_ACTION_PRE_UP:
|
|
case NM_DISPATCHER_ACTION_VPN_PRE_UP:
|
|
return &monitors[MONITOR_INDEX_PRE_UP];
|
|
case NM_DISPATCHER_ACTION_PRE_DOWN:
|
|
case NM_DISPATCHER_ACTION_VPN_PRE_DOWN:
|
|
return &monitors[MONITOR_INDEX_PRE_DOWN];
|
|
default:
|
|
return &monitors[MONITOR_INDEX_DEFAULT];
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump_proxy_to_props (NMProxyConfig *proxy, GVariantBuilder *builder)
|
|
{
|
|
const char *pac_url = NULL, *pac_script = NULL;
|
|
|
|
if (nm_proxy_config_get_method (proxy) == NM_PROXY_CONFIG_METHOD_NONE)
|
|
return;
|
|
|
|
pac_url = nm_proxy_config_get_pac_url (proxy);
|
|
if (pac_url) {
|
|
g_variant_builder_add (builder, "{sv}",
|
|
"pac-url",
|
|
g_variant_new_string (pac_url));
|
|
}
|
|
|
|
pac_script = nm_proxy_config_get_pac_script (proxy);
|
|
if (pac_script) {
|
|
g_variant_builder_add (builder, "{sv}",
|
|
"pac-script",
|
|
g_variant_new_string (pac_script));
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump_ip4_to_props (NMIP4Config *ip4, GVariantBuilder *builder)
|
|
{
|
|
GVariantBuilder int_builder;
|
|
NMDedupMultiIter ipconf_iter;
|
|
gboolean first;
|
|
guint n, i;
|
|
const NMPlatformIP4Address *addr;
|
|
const NMPlatformIP4Route *route;
|
|
guint32 array[4];
|
|
|
|
/* Addresses */
|
|
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aau"));
|
|
first = TRUE;
|
|
nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &addr) {
|
|
const NMPObject *default_route;
|
|
|
|
array[0] = addr->address;
|
|
array[1] = addr->plen;
|
|
array[2] = ( first
|
|
&& (default_route = nm_ip4_config_best_default_route_get (ip4)))
|
|
? NMP_OBJECT_CAST_IP4_ROUTE (default_route)->gateway
|
|
: (guint32) 0;
|
|
g_variant_builder_add (&int_builder, "@au",
|
|
g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
|
|
array, 3, sizeof (guint32)));
|
|
first = FALSE;
|
|
}
|
|
g_variant_builder_add (builder, "{sv}",
|
|
"addresses",
|
|
g_variant_builder_end (&int_builder));
|
|
|
|
/* DNS servers */
|
|
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("au"));
|
|
n = nm_ip4_config_get_num_nameservers (ip4);
|
|
for (i = 0; i < n; i++)
|
|
g_variant_builder_add (&int_builder, "u", nm_ip4_config_get_nameserver (ip4, i));
|
|
g_variant_builder_add (builder, "{sv}",
|
|
"nameservers",
|
|
g_variant_builder_end (&int_builder));
|
|
|
|
/* Search domains */
|
|
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("as"));
|
|
n = nm_ip4_config_get_num_domains (ip4);
|
|
for (i = 0; i < n; i++)
|
|
g_variant_builder_add (&int_builder, "s", nm_ip4_config_get_domain (ip4, i));
|
|
g_variant_builder_add (builder, "{sv}",
|
|
"domains",
|
|
g_variant_builder_end (&int_builder));
|
|
|
|
/* WINS servers */
|
|
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("au"));
|
|
n = nm_ip4_config_get_num_wins (ip4);
|
|
for (i = 0; i < n; i++)
|
|
g_variant_builder_add (&int_builder, "u", nm_ip4_config_get_wins (ip4, i));
|
|
g_variant_builder_add (builder, "{sv}",
|
|
"wins-servers",
|
|
g_variant_builder_end (&int_builder));
|
|
|
|
/* Static routes */
|
|
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aau"));
|
|
nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route) {
|
|
if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
|
|
continue;
|
|
array[0] = route->network;
|
|
array[1] = route->plen;
|
|
array[2] = route->gateway;
|
|
array[3] = route->metric;
|
|
g_variant_builder_add (&int_builder, "@au",
|
|
g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
|
|
array, 4, sizeof (guint32)));
|
|
}
|
|
g_variant_builder_add (builder, "{sv}",
|
|
"routes",
|
|
g_variant_builder_end (&int_builder));
|
|
}
|
|
|
|
static void
|
|
dump_ip6_to_props (NMIP6Config *ip6, GVariantBuilder *builder)
|
|
{
|
|
GVariantBuilder int_builder;
|
|
NMDedupMultiIter ipconf_iter;
|
|
guint n, i;
|
|
gboolean first;
|
|
const NMPlatformIP6Address *addr;
|
|
const NMPlatformIP6Route *route;
|
|
GVariant *ip, *gw;
|
|
|
|
/* Addresses */
|
|
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("a(ayuay)"));
|
|
|
|
first = TRUE;
|
|
nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &addr) {
|
|
const NMPObject *default_route;
|
|
|
|
ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
|
&addr->address,
|
|
sizeof (struct in6_addr), 1);
|
|
gw = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
|
( first
|
|
&& (default_route = nm_ip6_config_best_default_route_get (ip6)))
|
|
? &NMP_OBJECT_CAST_IP6_ROUTE (default_route)->gateway
|
|
: &in6addr_any,
|
|
sizeof (struct in6_addr), 1);
|
|
g_variant_builder_add (&int_builder, "(@ayu@ay)", ip, addr->plen, gw);
|
|
first = FALSE;
|
|
}
|
|
g_variant_builder_add (builder, "{sv}",
|
|
"addresses",
|
|
g_variant_builder_end (&int_builder));
|
|
|
|
/* DNS servers */
|
|
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aay"));
|
|
n = nm_ip6_config_get_num_nameservers (ip6);
|
|
for (i = 0; i < n; i++) {
|
|
ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
|
nm_ip6_config_get_nameserver (ip6, i),
|
|
sizeof (struct in6_addr), 1);
|
|
g_variant_builder_add (&int_builder, "@ay", ip);
|
|
}
|
|
g_variant_builder_add (builder, "{sv}",
|
|
"nameservers",
|
|
g_variant_builder_end (&int_builder));
|
|
|
|
/* Search domains */
|
|
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("as"));
|
|
n = nm_ip6_config_get_num_domains (ip6);
|
|
for (i = 0; i < n; i++)
|
|
g_variant_builder_add (&int_builder, "s", nm_ip6_config_get_domain (ip6, i));
|
|
g_variant_builder_add (builder, "{sv}",
|
|
"domains",
|
|
g_variant_builder_end (&int_builder));
|
|
|
|
/* Static routes */
|
|
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("a(ayuayu)"));
|
|
nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route) {
|
|
if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
|
|
continue;
|
|
ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
|
&route->network,
|
|
sizeof (struct in6_addr), 1);
|
|
gw = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
|
&route->gateway,
|
|
sizeof (struct in6_addr), 1);
|
|
g_variant_builder_add (&int_builder, "(@ayu@ayu)", ip, route->plen, gw, route->metric);
|
|
}
|
|
g_variant_builder_add (builder, "{sv}",
|
|
"routes",
|
|
g_variant_builder_end (&int_builder));
|
|
}
|
|
|
|
static void
|
|
fill_device_props (NMDevice *device,
|
|
GVariantBuilder *dev_builder,
|
|
GVariantBuilder *proxy_builder,
|
|
GVariantBuilder *ip4_builder,
|
|
GVariantBuilder *ip6_builder,
|
|
GVariant **dhcp4_props,
|
|
GVariant **dhcp6_props)
|
|
{
|
|
NMProxyConfig *proxy_config;
|
|
NMIP4Config *ip4_config;
|
|
NMIP6Config *ip6_config;
|
|
NMDhcp4Config *dhcp4_config;
|
|
NMDhcp6Config *dhcp6_config;
|
|
|
|
/* If the action is for a VPN, send the VPN's IP interface instead of the device's */
|
|
g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_IP_INTERFACE,
|
|
g_variant_new_string (nm_device_get_ip_iface (device)));
|
|
g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_INTERFACE,
|
|
g_variant_new_string (nm_device_get_iface (device)));
|
|
g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_TYPE,
|
|
g_variant_new_uint32 (nm_device_get_device_type (device)));
|
|
g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_STATE,
|
|
g_variant_new_uint32 (nm_device_get_state (device)));
|
|
if (nm_dbus_object_is_exported (NM_DBUS_OBJECT (device))) {
|
|
g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_PATH,
|
|
g_variant_new_object_path (nm_dbus_object_get_path (NM_DBUS_OBJECT (device))));
|
|
}
|
|
|
|
proxy_config = nm_device_get_proxy_config (device);
|
|
if (proxy_config)
|
|
dump_proxy_to_props (proxy_config, proxy_builder);
|
|
|
|
ip4_config = nm_device_get_ip4_config (device);
|
|
if (ip4_config)
|
|
dump_ip4_to_props (ip4_config, ip4_builder);
|
|
|
|
ip6_config = nm_device_get_ip6_config (device);
|
|
if (ip6_config)
|
|
dump_ip6_to_props (ip6_config, ip6_builder);
|
|
|
|
dhcp4_config = nm_device_get_dhcp4_config (device);
|
|
if (dhcp4_config)
|
|
*dhcp4_props = nm_dhcp4_config_get_options (dhcp4_config);
|
|
|
|
dhcp6_config = nm_device_get_dhcp6_config (device);
|
|
if (dhcp6_config)
|
|
*dhcp6_props = nm_dhcp6_config_get_options (dhcp6_config);
|
|
}
|
|
|
|
static void
|
|
fill_vpn_props (NMProxyConfig *proxy_config,
|
|
NMIP4Config *ip4_config,
|
|
NMIP6Config *ip6_config,
|
|
GVariantBuilder *proxy_builder,
|
|
GVariantBuilder *ip4_builder,
|
|
GVariantBuilder *ip6_builder)
|
|
{
|
|
if (proxy_config)
|
|
dump_proxy_to_props (proxy_config, proxy_builder);
|
|
if (ip4_config)
|
|
dump_ip4_to_props (ip4_config, ip4_builder);
|
|
if (ip6_config)
|
|
dump_ip6_to_props (ip6_config, ip6_builder);
|
|
}
|
|
|
|
typedef struct {
|
|
NMDispatcherAction action;
|
|
guint request_id;
|
|
NMDispatcherFunc callback;
|
|
gpointer user_data;
|
|
guint idle_id;
|
|
} DispatchInfo;
|
|
|
|
static void
|
|
dispatcher_info_free (DispatchInfo *info)
|
|
{
|
|
if (info->idle_id)
|
|
g_source_remove (info->idle_id);
|
|
g_free (info);
|
|
}
|
|
|
|
static void
|
|
_ensure_requests (void)
|
|
{
|
|
if (G_UNLIKELY (requests == NULL)) {
|
|
requests = g_hash_table_new_full (nm_direct_hash,
|
|
NULL,
|
|
NULL,
|
|
(GDestroyNotify) dispatcher_info_free);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dispatcher_info_cleanup (DispatchInfo *info)
|
|
{
|
|
g_hash_table_remove (requests, GUINT_TO_POINTER (info->request_id));
|
|
}
|
|
|
|
static const char *
|
|
dispatch_result_to_string (DispatchResult result)
|
|
{
|
|
switch (result) {
|
|
case DISPATCH_RESULT_UNKNOWN:
|
|
return "unknown";
|
|
case DISPATCH_RESULT_SUCCESS:
|
|
return "success";
|
|
case DISPATCH_RESULT_EXEC_FAILED:
|
|
return "exec failed";
|
|
case DISPATCH_RESULT_FAILED:
|
|
return "failed";
|
|
case DISPATCH_RESULT_TIMEOUT:
|
|
return "timed out";
|
|
}
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static void
|
|
dispatcher_results_process (guint request_id, NMDispatcherAction action, GVariantIter *results)
|
|
{
|
|
const char *script, *err;
|
|
guint32 result;
|
|
const Monitor *monitor = _get_monitor_by_action (action);
|
|
|
|
g_return_if_fail (results != NULL);
|
|
|
|
if (g_variant_iter_n_children (results) == 0) {
|
|
_LOGD ("(%u) succeeded but no scripts invoked", request_id);
|
|
return;
|
|
}
|
|
|
|
while (g_variant_iter_next (results, "(&su&s)", &script, &result, &err)) {
|
|
const char *script_validation_msg = "";
|
|
|
|
if (!*script) {
|
|
script_validation_msg = " (path is NULL)";
|
|
script = "(unknown)";
|
|
} else if (!strncmp (script, monitor->dir, monitor->dir_len) /* check: prefixed by script directory */
|
|
&& script[monitor->dir_len] == '/' && script[monitor->dir_len+1] /* check: with additional "/?" */
|
|
&& !strchr (&script[monitor->dir_len+1], '/')) { /* check: and no further '/' */
|
|
/* we expect the script to lie inside monitor->dir. If it does,
|
|
* strip the directory name. Otherwise show the full path and a warning. */
|
|
script += monitor->dir_len + 1;
|
|
} else
|
|
script_validation_msg = " (unexpected path)";
|
|
|
|
if (result == DISPATCH_RESULT_SUCCESS) {
|
|
_LOGD ("(%u) %s succeeded%s",
|
|
request_id,
|
|
script, script_validation_msg);
|
|
} else {
|
|
_LOGW ("(%u) %s failed (%s): %s%s",
|
|
request_id,
|
|
script,
|
|
dispatch_result_to_string (result),
|
|
err,
|
|
script_validation_msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
dispatcher_done_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
|
|
{
|
|
DispatchInfo *info = user_data;
|
|
GVariant *ret;
|
|
GVariantIter *results;
|
|
GError *error = NULL;
|
|
|
|
ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result,
|
|
G_VARIANT_TYPE ("(a(sus))"),
|
|
&error);
|
|
if (ret) {
|
|
g_variant_get (ret, "(a(sus))", &results);
|
|
dispatcher_results_process (info->request_id, info->action, results);
|
|
g_variant_iter_free (results);
|
|
g_variant_unref (ret);
|
|
} else {
|
|
if (_nm_dbus_error_has_name (error, "org.freedesktop.systemd1.LoadFailed")) {
|
|
g_dbus_error_strip_remote_error (error);
|
|
_LOGW ("(%u) failed to call dispatcher scripts: %s",
|
|
info->request_id, error->message);
|
|
} else {
|
|
_LOGD ("(%u) failed to call dispatcher scripts: %s",
|
|
info->request_id, error->message);
|
|
}
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
if (info->callback)
|
|
info->callback (info->request_id, info->user_data);
|
|
|
|
dispatcher_info_cleanup (info);
|
|
}
|
|
|
|
static const char *action_table[] = {
|
|
[NM_DISPATCHER_ACTION_HOSTNAME] = NMD_ACTION_HOSTNAME,
|
|
[NM_DISPATCHER_ACTION_PRE_UP] = NMD_ACTION_PRE_UP,
|
|
[NM_DISPATCHER_ACTION_UP] = NMD_ACTION_UP,
|
|
[NM_DISPATCHER_ACTION_PRE_DOWN] = NMD_ACTION_PRE_DOWN,
|
|
[NM_DISPATCHER_ACTION_DOWN] = NMD_ACTION_DOWN,
|
|
[NM_DISPATCHER_ACTION_VPN_PRE_UP] = NMD_ACTION_VPN_PRE_UP,
|
|
[NM_DISPATCHER_ACTION_VPN_UP] = NMD_ACTION_VPN_UP,
|
|
[NM_DISPATCHER_ACTION_VPN_PRE_DOWN] = NMD_ACTION_VPN_PRE_DOWN,
|
|
[NM_DISPATCHER_ACTION_VPN_DOWN] = NMD_ACTION_VPN_DOWN,
|
|
[NM_DISPATCHER_ACTION_DHCP4_CHANGE] = NMD_ACTION_DHCP4_CHANGE,
|
|
[NM_DISPATCHER_ACTION_DHCP6_CHANGE] = NMD_ACTION_DHCP6_CHANGE,
|
|
[NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE] = NMD_ACTION_CONNECTIVITY_CHANGE
|
|
};
|
|
|
|
static const char *
|
|
action_to_string (NMDispatcherAction action)
|
|
{
|
|
g_assert ((gsize) action < G_N_ELEMENTS (action_table));
|
|
return action_table[action];
|
|
}
|
|
|
|
static gboolean
|
|
dispatcher_idle_cb (gpointer user_data)
|
|
{
|
|
DispatchInfo *info = user_data;
|
|
|
|
info->idle_id = 0;
|
|
if (info->callback)
|
|
info->callback (info->request_id, info->user_data);
|
|
dispatcher_info_cleanup (info);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
_dispatcher_call (NMDispatcherAction action,
|
|
gboolean blocking,
|
|
NMDevice *device,
|
|
NMSettingsConnection *settings_connection,
|
|
NMConnection *applied_connection,
|
|
gboolean activation_type_external,
|
|
NMConnectivityState connectivity_state,
|
|
const char *vpn_iface,
|
|
NMProxyConfig *vpn_proxy_config,
|
|
NMIP4Config *vpn_ip4_config,
|
|
NMIP6Config *vpn_ip6_config,
|
|
NMDispatcherFunc callback,
|
|
gpointer user_data,
|
|
guint *out_call_id)
|
|
{
|
|
GVariant *connection_dict;
|
|
GVariantBuilder connection_props;
|
|
GVariantBuilder device_props;
|
|
GVariantBuilder device_proxy_props;
|
|
GVariantBuilder device_ip4_props;
|
|
GVariantBuilder device_ip6_props;
|
|
GVariant *device_dhcp4_props = NULL;
|
|
GVariant *device_dhcp6_props = NULL;
|
|
GVariantBuilder vpn_proxy_props;
|
|
GVariantBuilder vpn_ip4_props;
|
|
GVariantBuilder vpn_ip6_props;
|
|
DispatchInfo *info = NULL;
|
|
gboolean success = FALSE;
|
|
GError *error = NULL;
|
|
static guint request_counter = 0;
|
|
guint reqid = ++request_counter;
|
|
const char *connectivity_state_string = "UNKNOWN";
|
|
|
|
if (!dispatcher_proxy)
|
|
return FALSE;
|
|
|
|
/* Wrapping protection */
|
|
if (G_UNLIKELY (!reqid))
|
|
reqid = ++request_counter;
|
|
|
|
g_assert (!blocking || (!callback && !user_data));
|
|
|
|
_ensure_requests ();
|
|
|
|
/* All actions except 'hostname' and 'connectivity-change' require a device */
|
|
if ( action == NM_DISPATCHER_ACTION_HOSTNAME
|
|
|| action == NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE) {
|
|
_LOGD ("(%u) dispatching action '%s'%s",
|
|
reqid, action_to_string (action),
|
|
blocking
|
|
? " (blocking)"
|
|
: (callback ? " (with callback)" : ""));
|
|
} else {
|
|
g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
|
|
|
|
_LOGD ("(%u) (%s) dispatching action '%s'%s",
|
|
reqid,
|
|
vpn_iface ?: nm_device_get_iface(device),
|
|
action_to_string (action),
|
|
blocking
|
|
? " (blocking)"
|
|
: (callback ? " (with callback)" : ""));
|
|
}
|
|
|
|
if (!_get_monitor_by_action(action)->has_scripts) {
|
|
if (blocking == FALSE && (out_call_id || callback)) {
|
|
info = g_malloc0 (sizeof (*info));
|
|
info->action = action;
|
|
info->request_id = reqid;
|
|
info->callback = callback;
|
|
info->user_data = user_data;
|
|
info->idle_id = g_idle_add (dispatcher_idle_cb, info);
|
|
_LOGD ("(%u) simulate request; no scripts in %s", reqid, _get_monitor_by_action(action)->dir);
|
|
} else
|
|
_LOGD ("(%u) ignoring request; no scripts in %s", reqid, _get_monitor_by_action(action)->dir);
|
|
success = TRUE;
|
|
goto done;
|
|
}
|
|
|
|
if (applied_connection)
|
|
connection_dict = nm_connection_to_dbus (applied_connection, NM_CONNECTION_SERIALIZE_NO_SECRETS);
|
|
else
|
|
connection_dict = g_variant_new_array (G_VARIANT_TYPE ("{sa{sv}}"), NULL, 0);
|
|
|
|
g_variant_builder_init (&connection_props, G_VARIANT_TYPE_VARDICT);
|
|
if (settings_connection) {
|
|
const char *connection_path;
|
|
const char *filename;
|
|
|
|
connection_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (settings_connection));
|
|
if (connection_path) {
|
|
g_variant_builder_add (&connection_props, "{sv}",
|
|
NMD_CONNECTION_PROPS_PATH,
|
|
g_variant_new_object_path (connection_path));
|
|
}
|
|
filename = nm_settings_connection_get_filename (settings_connection);
|
|
if (filename) {
|
|
g_variant_builder_add (&connection_props, "{sv}",
|
|
NMD_CONNECTION_PROPS_FILENAME,
|
|
g_variant_new_string (filename));
|
|
}
|
|
if (activation_type_external) {
|
|
g_variant_builder_add (&connection_props, "{sv}",
|
|
NMD_CONNECTION_PROPS_EXTERNAL,
|
|
g_variant_new_boolean (TRUE));
|
|
}
|
|
}
|
|
|
|
g_variant_builder_init (&device_props, G_VARIANT_TYPE_VARDICT);
|
|
g_variant_builder_init (&device_proxy_props, G_VARIANT_TYPE_VARDICT);
|
|
g_variant_builder_init (&device_ip4_props, G_VARIANT_TYPE_VARDICT);
|
|
g_variant_builder_init (&device_ip6_props, G_VARIANT_TYPE_VARDICT);
|
|
g_variant_builder_init (&vpn_proxy_props, G_VARIANT_TYPE_VARDICT);
|
|
g_variant_builder_init (&vpn_ip4_props, G_VARIANT_TYPE_VARDICT);
|
|
g_variant_builder_init (&vpn_ip6_props, G_VARIANT_TYPE_VARDICT);
|
|
|
|
/* hostname and connectivity-change actions don't send device data */
|
|
if ( action != NM_DISPATCHER_ACTION_HOSTNAME
|
|
&& action != NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE) {
|
|
fill_device_props (device,
|
|
&device_props,
|
|
&device_proxy_props,
|
|
&device_ip4_props,
|
|
&device_ip6_props,
|
|
&device_dhcp4_props,
|
|
&device_dhcp6_props);
|
|
if (vpn_ip4_config || vpn_ip6_config) {
|
|
fill_vpn_props (vpn_proxy_config,
|
|
vpn_ip4_config,
|
|
vpn_ip6_config,
|
|
&vpn_proxy_props,
|
|
&vpn_ip4_props,
|
|
&vpn_ip6_props);
|
|
}
|
|
}
|
|
|
|
if (!device_dhcp4_props)
|
|
device_dhcp4_props = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
|
|
if (!device_dhcp6_props)
|
|
device_dhcp6_props = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
|
|
|
|
connectivity_state_string = nm_connectivity_state_to_string (connectivity_state);
|
|
|
|
/* Send the action to the dispatcher */
|
|
if (blocking) {
|
|
GVariant *ret;
|
|
GVariantIter *results;
|
|
|
|
ret = _nm_dbus_proxy_call_sync (dispatcher_proxy, "Action",
|
|
g_variant_new ("(s@a{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}@a{sv}@a{sv}ssa{sv}a{sv}a{sv}b)",
|
|
action_to_string (action),
|
|
connection_dict,
|
|
&connection_props,
|
|
&device_props,
|
|
&device_proxy_props,
|
|
&device_ip4_props,
|
|
&device_ip6_props,
|
|
device_dhcp4_props,
|
|
device_dhcp6_props,
|
|
connectivity_state_string,
|
|
vpn_iface ?: "",
|
|
&vpn_proxy_props,
|
|
&vpn_ip4_props,
|
|
&vpn_ip6_props,
|
|
nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH)),
|
|
G_VARIANT_TYPE ("(a(sus))"),
|
|
G_DBUS_CALL_FLAGS_NONE, CALL_TIMEOUT,
|
|
NULL, &error);
|
|
if (ret) {
|
|
g_variant_get (ret, "(a(sus))", &results);
|
|
dispatcher_results_process (reqid, action, results);
|
|
g_variant_iter_free (results);
|
|
g_variant_unref (ret);
|
|
success = TRUE;
|
|
} else {
|
|
g_dbus_error_strip_remote_error (error);
|
|
_LOGW ("(%u) failed: %s", reqid, error->message);
|
|
g_clear_error (&error);
|
|
success = FALSE;
|
|
}
|
|
} else {
|
|
info = g_malloc0 (sizeof (*info));
|
|
info->action = action;
|
|
info->request_id = reqid;
|
|
info->callback = callback;
|
|
info->user_data = user_data;
|
|
g_dbus_proxy_call (dispatcher_proxy, "Action",
|
|
g_variant_new ("(s@a{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}@a{sv}@a{sv}ssa{sv}a{sv}a{sv}b)",
|
|
action_to_string (action),
|
|
connection_dict,
|
|
&connection_props,
|
|
&device_props,
|
|
&device_proxy_props,
|
|
&device_ip4_props,
|
|
&device_ip6_props,
|
|
device_dhcp4_props,
|
|
device_dhcp6_props,
|
|
connectivity_state_string,
|
|
vpn_iface ?: "",
|
|
&vpn_proxy_props,
|
|
&vpn_ip4_props,
|
|
&vpn_ip6_props,
|
|
nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH)),
|
|
G_DBUS_CALL_FLAGS_NONE, CALL_TIMEOUT,
|
|
NULL, dispatcher_done_cb, info);
|
|
success = TRUE;
|
|
}
|
|
|
|
g_variant_unref (device_dhcp4_props);
|
|
g_variant_unref (device_dhcp6_props);
|
|
|
|
done:
|
|
if (success && info) {
|
|
/* Track the request in case of cancelation */
|
|
g_hash_table_insert (requests, GUINT_TO_POINTER (info->request_id), info);
|
|
if (out_call_id)
|
|
*out_call_id = info->request_id;
|
|
} else if (out_call_id)
|
|
*out_call_id = 0;
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* nm_dispatcher_call_hostname:
|
|
* @callback: a caller-supplied callback to execute when done
|
|
* @user_data: caller-supplied pointer passed to @callback
|
|
* @out_call_id: on success, a call identifier which can be passed to
|
|
* nm_dispatcher_call_cancel()
|
|
*
|
|
* This method always invokes the dispatcher action asynchronously.
|
|
*
|
|
* Returns: %TRUE if the action was dispatched, %FALSE on failure
|
|
*/
|
|
gboolean
|
|
nm_dispatcher_call_hostname (NMDispatcherFunc callback,
|
|
gpointer user_data,
|
|
guint *out_call_id)
|
|
{
|
|
return _dispatcher_call (NM_DISPATCHER_ACTION_HOSTNAME, FALSE,
|
|
NULL, NULL, NULL, FALSE,
|
|
NM_CONNECTIVITY_UNKNOWN,
|
|
NULL, NULL, NULL, NULL,
|
|
callback, user_data, out_call_id);
|
|
}
|
|
|
|
/**
|
|
* nm_dispatcher_call_device:
|
|
* @action: the %NMDispatcherAction
|
|
* @device: the #NMDevice the action applies to
|
|
* @act_request: the #NMActRequest for the action. If %NULL, use the
|
|
* current request of the device.
|
|
* @callback: a caller-supplied callback to execute when done
|
|
* @user_data: caller-supplied pointer passed to @callback
|
|
* @out_call_id: on success, a call identifier which can be passed to
|
|
* nm_dispatcher_call_cancel()
|
|
*
|
|
* This method always invokes the device dispatcher action asynchronously. To ignore
|
|
* the result, pass %NULL to @callback.
|
|
*
|
|
* Returns: %TRUE if the action was dispatched, %FALSE on failure
|
|
*/
|
|
gboolean
|
|
nm_dispatcher_call_device (NMDispatcherAction action,
|
|
NMDevice *device,
|
|
NMActRequest *act_request,
|
|
NMDispatcherFunc callback,
|
|
gpointer user_data,
|
|
guint *out_call_id)
|
|
{
|
|
nm_assert (NM_IS_DEVICE (device));
|
|
if (!act_request) {
|
|
act_request = nm_device_get_act_request (device);
|
|
if (!act_request)
|
|
return FALSE;
|
|
}
|
|
nm_assert (NM_IN_SET (nm_active_connection_get_device (NM_ACTIVE_CONNECTION (act_request)), NULL, device));
|
|
return _dispatcher_call (action, FALSE,
|
|
device,
|
|
nm_act_request_get_settings_connection (act_request),
|
|
nm_act_request_get_applied_connection (act_request),
|
|
nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (act_request)) == NM_ACTIVATION_TYPE_EXTERNAL,
|
|
NM_CONNECTIVITY_UNKNOWN,
|
|
NULL, NULL, NULL, NULL,
|
|
callback, user_data, out_call_id);
|
|
}
|
|
|
|
/**
|
|
* nm_dispatcher_call_device_sync():
|
|
* @action: the %NMDispatcherAction
|
|
* @device: the #NMDevice the action applies to
|
|
* @act_request: the #NMActRequest for the action. If %NULL, use the
|
|
* current request of the device.
|
|
*
|
|
* This method always invokes the dispatcher action synchronously and it may
|
|
* take a long time to return.
|
|
*
|
|
* Returns: %TRUE if the action was dispatched, %FALSE on failure
|
|
*/
|
|
gboolean
|
|
nm_dispatcher_call_device_sync (NMDispatcherAction action,
|
|
NMDevice *device,
|
|
NMActRequest *act_request)
|
|
{
|
|
nm_assert (NM_IS_DEVICE (device));
|
|
if (!act_request) {
|
|
act_request = nm_device_get_act_request (device);
|
|
if (!act_request)
|
|
return FALSE;
|
|
}
|
|
nm_assert (NM_IN_SET (nm_active_connection_get_device (NM_ACTIVE_CONNECTION (act_request)), NULL, device));
|
|
return _dispatcher_call (action, TRUE,
|
|
device,
|
|
nm_act_request_get_settings_connection (act_request),
|
|
nm_act_request_get_applied_connection (act_request),
|
|
nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (act_request)) == NM_ACTIVATION_TYPE_EXTERNAL,
|
|
NM_CONNECTIVITY_UNKNOWN,
|
|
NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL);
|
|
}
|
|
|
|
/**
|
|
* nm_dispatcher_call_vpn():
|
|
* @action: the %NMDispatcherAction
|
|
* @settings_connection: the #NMSettingsConnection the action applies to
|
|
* @applied_connection: the currently applied connection
|
|
* @parent_device: the parent #NMDevice of the VPN connection
|
|
* @vpn_iface: the IP interface of the VPN tunnel, if any
|
|
* @vpn_proxy_config: the #NMProxyConfig of the VPN connection
|
|
* @vpn_ip4_config: the #NMIP4Config of the VPN connection
|
|
* @vpn_ip6_config: the #NMIP6Config of the VPN connection
|
|
* @callback: a caller-supplied callback to execute when done
|
|
* @user_data: caller-supplied pointer passed to @callback
|
|
* @out_call_id: on success, a call identifier which can be passed to
|
|
* nm_dispatcher_call_cancel()
|
|
*
|
|
* This method always invokes the dispatcher action asynchronously. To ignore
|
|
* the result, pass %NULL to @callback.
|
|
*
|
|
* Returns: %TRUE if the action was dispatched, %FALSE on failure
|
|
*/
|
|
gboolean
|
|
nm_dispatcher_call_vpn (NMDispatcherAction action,
|
|
NMSettingsConnection *settings_connection,
|
|
NMConnection *applied_connection,
|
|
NMDevice *parent_device,
|
|
const char *vpn_iface,
|
|
NMProxyConfig *vpn_proxy_config,
|
|
NMIP4Config *vpn_ip4_config,
|
|
NMIP6Config *vpn_ip6_config,
|
|
NMDispatcherFunc callback,
|
|
gpointer user_data,
|
|
guint *out_call_id)
|
|
{
|
|
return _dispatcher_call (action, FALSE,
|
|
parent_device,
|
|
settings_connection,
|
|
applied_connection,
|
|
FALSE,
|
|
NM_CONNECTIVITY_UNKNOWN,
|
|
vpn_iface, vpn_proxy_config, vpn_ip4_config, vpn_ip6_config,
|
|
callback, user_data, out_call_id);
|
|
}
|
|
|
|
/**
|
|
* nm_dispatcher_call_vpn_sync():
|
|
* @action: the %NMDispatcherAction
|
|
* @settings_connection: the #NMSettingsConnection the action applies to
|
|
* @applied_connection: the currently applied connection
|
|
* @parent_device: the parent #NMDevice of the VPN connection
|
|
* @vpn_iface: the IP interface of the VPN tunnel, if any
|
|
* @vpn_proxy_config: the #NMProxyConfig of the VPN connection
|
|
* @vpn_ip4_config: the #NMIP4Config of the VPN connection
|
|
* @vpn_ip6_config: the #NMIP6Config of the VPN connection
|
|
*
|
|
* This method always invokes the dispatcher action synchronously and it may
|
|
* take a long time to return.
|
|
*
|
|
* Returns: %TRUE if the action was dispatched, %FALSE on failure
|
|
*/
|
|
gboolean
|
|
nm_dispatcher_call_vpn_sync (NMDispatcherAction action,
|
|
NMSettingsConnection *settings_connection,
|
|
NMConnection *applied_connection,
|
|
NMDevice *parent_device,
|
|
const char *vpn_iface,
|
|
NMProxyConfig *vpn_proxy_config,
|
|
NMIP4Config *vpn_ip4_config,
|
|
NMIP6Config *vpn_ip6_config)
|
|
{
|
|
return _dispatcher_call (action, TRUE,
|
|
parent_device,
|
|
settings_connection,
|
|
applied_connection,
|
|
FALSE,
|
|
NM_CONNECTIVITY_UNKNOWN,
|
|
vpn_iface, vpn_proxy_config, vpn_ip4_config, vpn_ip6_config,
|
|
NULL, NULL, NULL);
|
|
}
|
|
|
|
/**
|
|
* nm_dispatcher_call_connectivity():
|
|
* @connectivity_state: the #NMConnectivityState value
|
|
* @callback: a caller-supplied callback to execute when done
|
|
* @user_data: caller-supplied pointer passed to @callback
|
|
* @out_call_id: on success, a call identifier which can be passed to
|
|
* nm_dispatcher_call_cancel()
|
|
*
|
|
* This method does not block the caller.
|
|
*
|
|
* Returns: %TRUE if the action was dispatched, %FALSE on failure
|
|
*/
|
|
gboolean
|
|
nm_dispatcher_call_connectivity (NMConnectivityState connectivity_state,
|
|
NMDispatcherFunc callback,
|
|
gpointer user_data,
|
|
guint *out_call_id)
|
|
{
|
|
return _dispatcher_call (NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE, FALSE,
|
|
NULL, NULL, NULL, FALSE,
|
|
connectivity_state,
|
|
NULL, NULL, NULL, NULL,
|
|
callback, user_data, out_call_id);
|
|
}
|
|
|
|
void
|
|
nm_dispatcher_call_cancel (guint call_id)
|
|
{
|
|
DispatchInfo *info;
|
|
|
|
_ensure_requests ();
|
|
|
|
/* Canceling just means the callback doesn't get called, so set the
|
|
* DispatcherInfo's callback to NULL.
|
|
*/
|
|
info = g_hash_table_lookup (requests, GUINT_TO_POINTER (call_id));
|
|
g_return_if_fail (info);
|
|
|
|
if (info && info->callback) {
|
|
_LOGD ("(%u) cancelling dispatcher callback action", call_id);
|
|
info->callback = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
dispatcher_dir_changed (GFileMonitor *monitor,
|
|
GFile *file,
|
|
GFile *other_file,
|
|
GFileMonitorEvent event_type,
|
|
Monitor *item)
|
|
{
|
|
const char *name;
|
|
char *full_name;
|
|
GDir *dir;
|
|
GError *error = NULL;
|
|
|
|
dir = g_dir_open (item->dir, 0, &error);
|
|
if (dir) {
|
|
int errsv = 0;
|
|
|
|
item->has_scripts = FALSE;
|
|
errno = 0;
|
|
while (!item->has_scripts
|
|
&& (name = g_dir_read_name (dir))) {
|
|
full_name = g_build_filename (item->dir, name, NULL);
|
|
item->has_scripts = g_file_test (full_name, G_FILE_TEST_IS_EXECUTABLE);
|
|
g_free (full_name);
|
|
}
|
|
errsv = errno;
|
|
g_dir_close (dir);
|
|
if (item->has_scripts)
|
|
_LOGD ("%s script directory '%s' has scripts", item->description, item->dir);
|
|
else if (errsv == 0)
|
|
_LOGD ("%s script directory '%s' has no scripts", item->description, item->dir);
|
|
else {
|
|
_LOGD ("%s script directory '%s' error reading (%s)", item->description, item->dir, nm_strerror_native (errsv));
|
|
item->has_scripts = TRUE;
|
|
}
|
|
} else {
|
|
if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
|
|
_LOGD ("%s script directory '%s' does not exist", item->description, item->dir);
|
|
item->has_scripts = FALSE;
|
|
} else {
|
|
_LOGD ("%s script directory '%s' error (%s)", item->description, item->dir, error->message);
|
|
item->has_scripts = TRUE;
|
|
}
|
|
g_error_free (error);
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
nm_dispatcher_init (void)
|
|
{
|
|
GFile *file;
|
|
guint i;
|
|
GError *error = NULL;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (monitors); i++) {
|
|
file = g_file_new_for_path (monitors[i].dir);
|
|
monitors[i].monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
|
|
if (monitors[i].monitor) {
|
|
g_signal_connect (monitors[i].monitor, "changed", G_CALLBACK (dispatcher_dir_changed), &monitors[i]);
|
|
dispatcher_dir_changed (monitors[i].monitor, file, NULL, 0, &monitors[i]);
|
|
}
|
|
g_object_unref (file);
|
|
}
|
|
|
|
dispatcher_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
|
|
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
|
|
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
|
|
NULL,
|
|
NM_DISPATCHER_DBUS_SERVICE,
|
|
NM_DISPATCHER_DBUS_PATH,
|
|
NM_DISPATCHER_DBUS_INTERFACE,
|
|
NULL, &error);
|
|
if (!dispatcher_proxy) {
|
|
_LOGE ("could not get dispatcher proxy! %s", error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
}
|
|
|