/* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright (C) 2004 - 2018 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-dhcp-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__) #define _NMLOG2_DOMAIN LOGD_DISPATCH #define _NMLOG2(level, request_id, log_ifname, log_con_uuid, ...) \ nm_log((level), \ _NMLOG2_DOMAIN, \ (log_ifname), \ (log_con_uuid), \ "dispatcher: (%u) " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ (request_id) _NM_UTILS_MACRO_REST(__VA_ARGS__)) #define _NMLOG3_DOMAIN LOGD_DISPATCH #define _NMLOG3(level, call_id, ...) \ G_STMT_START \ { \ const NMDispatcherCallId *const _call_id = (call_id); \ \ _NMLOG2(level, \ _call_id->request_id, \ _call_id->log_ifname, \ _call_id->log_con_uuid, \ __VA_ARGS__); \ } \ G_STMT_END /*****************************************************************************/ struct NMDispatcherCallId { NMDispatcherFunc callback; gpointer user_data; const char * log_ifname; const char * log_con_uuid; NMDispatcherAction action; guint idle_id; guint32 request_id; char extra_strings[]; }; /*****************************************************************************/ /* FIXME(shutdown): on shutdown, we should not run dispatcher scripts synchronously. * Instead, we should of course still run them asynchronously. * * Also, we should wait for all pending requests to complete before exiting the main-loop * (with a watchdog). If we hit a timeout, we log a warning and quit (but leave the scripts * running). * * Finally, cleanup the global structures. */ static struct { GDBusConnection *dbus_connection; GHashTable * requests; guint request_id_counter; } gl; /*****************************************************************************/ static NMDispatcherCallId * dispatcher_call_id_new(guint32 request_id, NMDispatcherAction action, NMDispatcherFunc callback, gpointer user_data, const char * log_ifname, const char * log_con_uuid) { NMDispatcherCallId *call_id; gsize l_log_ifname; gsize l_log_con_uuid; char * extra_strings; l_log_ifname = log_ifname ? (strlen(log_ifname) + 1) : 0u; l_log_con_uuid = log_con_uuid ? (strlen(log_con_uuid) + 1) : 0u; call_id = g_malloc(sizeof(NMDispatcherCallId) + l_log_ifname + l_log_con_uuid); call_id->action = action; call_id->request_id = request_id; call_id->callback = callback; call_id->user_data = user_data; call_id->idle_id = 0; extra_strings = &call_id->extra_strings[0]; if (log_ifname) { call_id->log_ifname = extra_strings; memcpy(extra_strings, log_ifname, l_log_ifname); extra_strings += l_log_ifname; } else call_id->log_ifname = NULL; if (log_con_uuid) { call_id->log_con_uuid = extra_strings; memcpy(extra_strings, log_con_uuid, l_log_con_uuid); } else call_id->log_con_uuid = NULL; return call_id; } static void dispatcher_call_id_free(NMDispatcherCallId *call_id) { nm_clear_g_source(&call_id->idle_id); g_free(call_id); } /*****************************************************************************/ static void _init_dispatcher(void) { if (G_UNLIKELY(gl.requests == NULL)) { gl.requests = g_hash_table_new(nm_direct_hash, NULL); gl.dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); if (!gl.dbus_connection) _LOGD("No D-Bus connection to talk with NetworkManager-dispatcher service"); } } /*****************************************************************************/ 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_ip_to_props(NMIPConfig *ip, GVariantBuilder *builder) { const int addr_family = nm_ip_config_get_addr_family(ip); const int IS_IPv4 = NM_IS_IPv4(addr_family); const NMPObject *obj; GVariantBuilder int_builder; NMDedupMultiIter ipconf_iter; GVariant * var1; GVariant * var2; guint n; guint i; const NMPObject *default_route; if (IS_IPv4) g_variant_builder_init(&int_builder, G_VARIANT_TYPE("aau")); else g_variant_builder_init(&int_builder, G_VARIANT_TYPE("a(ayuay)")); default_route = nm_ip_config_best_default_route_get(ip); if (IS_IPv4) nm_ip_config_iter_ip4_address_init(&ipconf_iter, NM_IP4_CONFIG(ip)); else nm_ip_config_iter_ip6_address_init(&ipconf_iter, NM_IP6_CONFIG(ip)); while (nm_platform_dedup_multi_iter_next_obj(&ipconf_iter, &obj, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4))) { const NMPlatformIPXAddress *addr = NMP_OBJECT_CAST_IPX_ADDRESS(obj); if (IS_IPv4) { guint32 array[3]; in_addr_t gw; gw = 0u; if (default_route) { gw = NMP_OBJECT_CAST_IP4_ROUTE(default_route)->gateway; default_route = NULL; } array[0] = addr->a4.address; array[1] = addr->a4.plen; array[2] = gw; g_variant_builder_add( &int_builder, "@au", g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, array, 3, sizeof(guint32))); } else { const struct in6_addr *gw = &in6addr_any; if (default_route) { gw = &NMP_OBJECT_CAST_IP6_ROUTE(default_route)->gateway; default_route = NULL; } var1 = nm_g_variant_new_ay_in6addr(&addr->a6.address); var2 = nm_g_variant_new_ay_in6addr(gw); g_variant_builder_add(&int_builder, "(@ayu@ay)", var1, addr->a6.plen, var2); } } g_variant_builder_add(builder, "{sv}", "addresses", g_variant_builder_end(&int_builder)); if (IS_IPv4) g_variant_builder_init(&int_builder, G_VARIANT_TYPE("au")); else g_variant_builder_init(&int_builder, G_VARIANT_TYPE("aay")); n = nm_ip_config_get_num_nameservers(ip); for (i = 0; i < n; i++) { if (IS_IPv4) { g_variant_builder_add(&int_builder, "u", nm_ip4_config_get_nameserver(NM_IP4_CONFIG(ip), i)); } else { var1 = nm_g_variant_new_ay_in6addr(nm_ip6_config_get_nameserver(NM_IP6_CONFIG(ip), i)); g_variant_builder_add(&int_builder, "@ay", var1); } } g_variant_builder_add(builder, "{sv}", "nameservers", g_variant_builder_end(&int_builder)); g_variant_builder_init(&int_builder, G_VARIANT_TYPE("as")); n = nm_ip_config_get_num_domains(ip); for (i = 0; i < n; i++) g_variant_builder_add(&int_builder, "s", nm_ip_config_get_domain(ip, i)); g_variant_builder_add(builder, "{sv}", "domains", g_variant_builder_end(&int_builder)); if (IS_IPv4) { g_variant_builder_init(&int_builder, G_VARIANT_TYPE("au")); n = nm_ip4_config_get_num_wins(NM_IP4_CONFIG(ip)); for (i = 0; i < n; i++) g_variant_builder_add(&int_builder, "u", nm_ip4_config_get_wins(NM_IP4_CONFIG(ip), i)); g_variant_builder_add(builder, "{sv}", "wins-servers", g_variant_builder_end(&int_builder)); } if (IS_IPv4) g_variant_builder_init(&int_builder, G_VARIANT_TYPE("aau")); else g_variant_builder_init(&int_builder, G_VARIANT_TYPE("a(ayuayu)")); if (IS_IPv4) nm_ip_config_iter_ip4_route_init(&ipconf_iter, NM_IP4_CONFIG(ip)); else nm_ip_config_iter_ip6_route_init(&ipconf_iter, NM_IP6_CONFIG(ip)); while (nm_platform_dedup_multi_iter_next_obj(&ipconf_iter, &obj, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4))) { const NMPlatformIPXRoute *route = NMP_OBJECT_CAST_IPX_ROUTE(obj); if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route)) continue; if (IS_IPv4) { guint32 array[4]; array[0] = route->r4.network; array[1] = route->r4.plen; array[2] = route->r4.gateway; array[3] = route->r4.metric; g_variant_builder_add( &int_builder, "@au", g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, array, 4, sizeof(guint32))); } else { var1 = nm_g_variant_new_ay_in6addr(&route->r6.network); var2 = nm_g_variant_new_ay_in6addr(&route->r6.gateway); g_variant_builder_add(&int_builder, "(@ayu@ayu)", var1, route->r6.plen, var2, route->r6.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; NMDhcpConfig * dhcp_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_ip_to_props(NM_IP_CONFIG(ip4_config), ip4_builder); ip6_config = nm_device_get_ip6_config(device); if (ip6_config) dump_ip_to_props(NM_IP_CONFIG(ip6_config), ip6_builder); dhcp_config = nm_device_get_dhcp_config(device, AF_INET); if (dhcp_config) *dhcp4_props = nm_g_variant_ref(nm_dhcp_config_get_options(dhcp_config)); dhcp_config = nm_device_get_dhcp_config(device, AF_INET6); if (dhcp_config) *dhcp6_props = nm_g_variant_ref(nm_dhcp_config_get_options(dhcp_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_ip_to_props(NM_IP_CONFIG(ip4_config), ip4_builder); if (ip6_config) dump_ip_to_props(NM_IP_CONFIG(ip6_config), ip6_builder); } 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(guint32 request_id, const char *log_ifname, const char *log_con_uuid, GVariant * v_results) { nm_auto_free_variant_iter GVariantIter *results = NULL; const char * script, *err; guint32 result; g_variant_get(v_results, "(a(sus))", &results); if (g_variant_iter_n_children(results) == 0) { _LOG2D(request_id, log_ifname, log_con_uuid, "succeeded but no scripts invoked"); return; } while (g_variant_iter_next(results, "(&su&s)", &script, &result, &err)) { if (result == DISPATCH_RESULT_SUCCESS) { _LOG2D(request_id, log_ifname, log_con_uuid, "%s succeeded", script); } else { _LOG2W(request_id, log_ifname, log_con_uuid, "%s failed (%s): %s", script, dispatch_result_to_string(result), err); } } } static void dispatcher_done_cb(GObject *source, GAsyncResult *result, gpointer user_data) { gs_unref_variant GVariant *ret = NULL; gs_free_error GError *error = NULL; NMDispatcherCallId * call_id = user_data; nm_assert((gpointer) source == gl.dbus_connection); ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (!ret) { if (_nm_dbus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed")) { g_dbus_error_strip_remote_error(error); _LOG3W(call_id, "failed to call dispatcher scripts: %s", error->message); } else { _LOG3D(call_id, "failed to call dispatcher scripts: %s", error->message); } } else { dispatcher_results_process(call_id->request_id, call_id->log_ifname, call_id->log_con_uuid, ret); } g_hash_table_remove(gl.requests, call_id); if (call_id->callback) call_id->callback(call_id, call_id->user_data); dispatcher_call_id_free(call_id); } 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) { if (G_UNLIKELY((gsize) action >= G_N_ELEMENTS(action_table))) g_return_val_if_reached(NULL); return action_table[(gsize) action]; } 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, NMDispatcherCallId ** out_call_id) { GVariant * connection_dict; GVariantBuilder connection_props; GVariantBuilder device_props; GVariantBuilder device_proxy_props; GVariantBuilder device_ip4_props; GVariantBuilder device_ip6_props; gs_unref_variant GVariant *parameters_floating = NULL; gs_unref_variant GVariant *device_dhcp4_props = NULL; gs_unref_variant GVariant *device_dhcp6_props = NULL; GVariantBuilder vpn_proxy_props; GVariantBuilder vpn_ip4_props; GVariantBuilder vpn_ip6_props; NMDispatcherCallId * call_id; guint request_id; const char * connectivity_state_string = "UNKNOWN"; const char * log_ifname; const char * log_con_uuid; g_return_val_if_fail(!blocking || (!callback && !user_data), FALSE); NM_SET_OUT(out_call_id, NULL); _init_dispatcher(); if (!gl.dbus_connection) return FALSE; log_ifname = device ? nm_device_get_iface(device) : NULL; log_con_uuid = settings_connection ? nm_settings_connection_get_uuid(settings_connection) : NULL; request_id = ++gl.request_id_counter; if (G_UNLIKELY(!request_id)) request_id = ++gl.request_id_counter; /* All actions except 'hostname' and 'connectivity-change' require a device */ if (action == NM_DISPATCHER_ACTION_HOSTNAME || action == NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE) { _LOG2D(request_id, log_ifname, log_con_uuid, "dispatching action '%s'%s", action_to_string(action), blocking ? " (blocking)" : (callback ? " (with callback)" : "")); } else { g_return_val_if_fail(NM_IS_DEVICE(device), FALSE); _LOG2D(request_id, log_ifname, log_con_uuid, "(%s) dispatching action '%s'%s", vpn_iface ?: nm_device_get_iface(device), action_to_string(action), blocking ? " (blocking)" : (callback ? " (with callback)" : "")); } 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); } } connectivity_state_string = nm_connectivity_state_to_string(connectivity_state); parameters_floating = 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 ?: g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0), device_dhcp6_props ?: g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0), connectivity_state_string, vpn_iface ?: "", &vpn_proxy_props, &vpn_ip4_props, &vpn_ip6_props, nm_logging_enabled(LOGL_DEBUG, LOGD_DISPATCH)); /* Send the action to the dispatcher */ if (blocking) { gs_unref_variant GVariant *ret = NULL; gs_free_error GError *error = NULL; ret = g_dbus_connection_call_sync(gl.dbus_connection, NM_DISPATCHER_DBUS_SERVICE, NM_DISPATCHER_DBUS_PATH, NM_DISPATCHER_DBUS_INTERFACE, "Action", g_steal_pointer(¶meters_floating), G_VARIANT_TYPE("(a(sus))"), G_DBUS_CALL_FLAGS_NONE, CALL_TIMEOUT, NULL, &error); if (!ret) { g_dbus_error_strip_remote_error(error); _LOG2W(request_id, log_ifname, log_con_uuid, "failed: %s", error->message); return FALSE; } dispatcher_results_process(request_id, log_ifname, log_con_uuid, ret); return TRUE; } call_id = dispatcher_call_id_new(request_id, action, callback, user_data, log_ifname, log_con_uuid); g_dbus_connection_call(gl.dbus_connection, NM_DISPATCHER_DBUS_SERVICE, NM_DISPATCHER_DBUS_PATH, NM_DISPATCHER_DBUS_INTERFACE, "Action", g_steal_pointer(¶meters_floating), G_VARIANT_TYPE("(a(sus))"), G_DBUS_CALL_FLAGS_NONE, CALL_TIMEOUT, NULL, dispatcher_done_cb, call_id); g_hash_table_add(gl.requests, call_id); NM_SET_OUT(out_call_id, call_id); return TRUE; } /** * 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, NMDispatcherCallId **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, NMDispatcherCallId **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, NMDispatcherCallId ** 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, NMDispatcherCallId **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(NMDispatcherCallId *call_id) { if (!call_id || g_hash_table_lookup(gl.requests, call_id) != call_id || !call_id->callback) g_return_if_reached(); /* Canceling just means the callback doesn't get called, so set the * DispatcherInfo's callback to NULL. */ _LOG3D(call_id, "cancelling dispatcher callback action"); call_id->callback = NULL; }