NetworkManager/src/NetworkManagerPolicy.c
Dan Williams ec89663e7d 2008-03-26 Dan Williams <dcbw@redhat.com>
Rework VPN connection handling for a more consistent D-Bus API.  The
	VPNManager object has been removed, and active VPN connections are now the
	same as any other active connection.  The Manager object's ActivateConnection
	and DeactivateConnection methods are used to start and stop a VPN connection,
	and the VPNConnection objects are subclasses of the ActiveConnection objects.
	When activating a VPN connection, pass the path of the active connection
	to which the VPN connection is tied in the 'specific_object' argument.

	Consequently, the libnm-glib API has been reworked to match this arrangement,
	with the VPNManager object removed, and the NMVPNConnection objects now
	being subclasses of NMActiveConnection.



git-svn-id: http://svn-archive.gnome.org/svn/NetworkManager/trunk@3504 4912f4e0-d625-0410-9fb7-b9a5a253dbdc
2008-03-26 13:43:01 +00:00

590 lines
17 KiB
C

/* -*- Mode: C; tab-width: 5; indent-tabs-mode: t; c-basic-offset: 5 -*- */
/* NetworkManager -- Network link manager
*
* Dan Williams <dcbw@redhat.com>
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* (C) Copyright 2005 Red Hat, Inc.
*/
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/select.h>
#include <string.h>
#include "NetworkManagerPolicy.h"
#include "NetworkManagerUtils.h"
#include "NetworkManagerAP.h"
#include "nm-activation-request.h"
#include "nm-utils.h"
#include "nm-device-interface.h"
#include "nm-device.h"
#include "nm-device-802-11-wireless.h"
#include "nm-device-802-3-ethernet.h"
#include "nm-gsm-device.h"
#include "nm-cdma-device.h"
#include "nm-dbus-manager.h"
#include "nm-setting-ip4-config.h"
#include "nm-setting-connection.h"
#include "NetworkManagerSystem.h"
#include "nm-named-manager.h"
struct NMPolicy {
NMManager *manager;
guint update_state_id;
GSList *pending_activation_checks;
GSList *signal_ids;
GSList *dev_signal_ids;
NMDevice *default_device;
};
#define INVALID_TAG "invalid"
static const char *
get_connection_id (NMConnection *connection)
{
NMSettingConnection *s_con;
g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
g_return_val_if_fail (s_con != NULL, NULL);
return s_con->id;
}
static void
update_default_route (NMPolicy *policy, NMDevice *new)
{
const char *ip_iface;
/* FIXME: Not sure if the following makes any sense. */
/* If iface and ip_iface are the same, it's a regular network device and we
treat it as such. However, if they differ, it's most likely something like
a serial device with ppp interface, so route all the traffic to it. */
ip_iface = nm_device_get_ip_iface (new);
if (strcmp (ip_iface, nm_device_get_iface (new))) {
nm_system_device_replace_default_route (ip_iface, 0, 0);
} else {
NMIP4Config *config;
config = nm_device_get_ip4_config (new);
nm_system_device_replace_default_route (ip_iface, nm_ip4_config_get_gateway (config),
nm_ip4_config_get_mss (config));
}
}
static guint32
get_device_priority (NMDevice *dev)
{
if (NM_IS_CDMA_DEVICE (dev))
return 2;
if (NM_IS_GSM_DEVICE (dev))
return 3;
if (NM_IS_DEVICE_802_11_WIRELESS (dev))
return 4;
if (NM_IS_DEVICE_802_3_ETHERNET (dev))
return 5;
return 1;
}
static void
update_routing_and_dns (NMPolicy *policy, gboolean force_update)
{
NMDevice *best = NULL;
guint32 best_prio = 0;
GSList *devices, *iter;
NMNamedManager *named_mgr;
NMIP4Config *config;
devices = nm_manager_get_devices (policy->manager);
for (iter = devices; iter; iter = g_slist_next (iter)) {
NMDevice *dev = NM_DEVICE (iter->data);
NMActRequest *req;
NMConnection *connection;
NMSettingIP4Config *s_ip4;
guint32 prio;
if ( (nm_device_get_state (dev) != NM_DEVICE_STATE_ACTIVATED)
|| !nm_device_get_ip4_config (dev))
continue;
req = nm_device_get_act_request (dev);
g_assert (req);
connection = nm_act_request_get_connection (req);
g_assert (connection);
/* Never set the default route through an IPv4LL-addressed device */
s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
if (s_ip4 && !strcmp (s_ip4->method, NM_SETTING_IP4_CONFIG_METHOD_AUTOIP))
continue;
prio = get_device_priority (dev);
if (prio > best_prio) {
best = dev;
best_prio = prio;
}
}
if (!best)
goto out;
if (!force_update && (best == policy->default_device))
goto out;
update_default_route (policy, best);
named_mgr = nm_named_manager_get ();
config = nm_device_get_ip4_config (best);
nm_named_manager_add_ip4_config (named_mgr, config, NM_NAMED_IP_CONFIG_TYPE_BEST_DEVICE);
g_object_unref (named_mgr);
nm_info ("Policy set (%s) as default device for routing and DNS.",
nm_device_get_iface (best));
out:
policy->default_device = best;
}
typedef struct {
NMPolicy *policy;
NMDevice *device;
guint id;
} ActivateData;
static gboolean
auto_activate_device (gpointer user_data)
{
ActivateData *data = (ActivateData *) user_data;
NMPolicy *policy;
NMConnection *best_connection;
char *specific_object = NULL;
GSList *connections, *iter;
g_assert (data);
policy = data->policy;
// FIXME: if a device is already activating (or activated) with a connection
// but another connection now overrides the current one for that device,
// deactivate the device and activate the new connection instead of just
// bailing if the device is already active
if (nm_device_get_act_request (data->device))
goto out;
/* System connections first, then user connections */
connections = nm_manager_get_connections (policy->manager, NM_CONNECTION_SCOPE_SYSTEM);
connections = g_slist_concat (connections, nm_manager_get_connections (policy->manager, NM_CONNECTION_SCOPE_USER));
/* Remove connections that are in the invalid list. */
iter = connections;
while (iter) {
NMConnection *iter_connection = NM_CONNECTION (iter->data);
GSList *next = g_slist_next (iter);
if (g_object_get_data (G_OBJECT (iter_connection), INVALID_TAG)) {
connections = g_slist_remove_link (connections, iter);
g_object_unref (iter_connection);
g_slist_free (iter);
}
iter = next;
}
best_connection = nm_device_get_best_auto_connection (data->device, connections, &specific_object);
if (best_connection) {
GError *error = NULL;
const char *device_path;
device_path = nm_device_get_udi (data->device);
if (!nm_manager_activate_connection (policy->manager,
best_connection,
specific_object,
device_path,
FALSE,
&error)) {
NMSettingConnection *s_con;
s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (best_connection, NM_TYPE_SETTING_CONNECTION));
g_assert (s_con);
nm_warning ("Connection '%s' auto-activation failed: (%d) %s",
s_con->id, error->code, error->message);
g_error_free (error);
}
}
g_slist_foreach (connections, (GFunc) g_object_unref, NULL);
g_slist_free (connections);
out:
/* Remove this call's handler ID */
policy->pending_activation_checks = g_slist_remove (policy->pending_activation_checks, data);
g_object_unref (data->device);
g_free (data);
return FALSE;
}
/*****************************************************************************/
static void
global_state_changed (NMManager *manager, NMState state, gpointer user_data)
{
if (state == NM_STATE_CONNECTED)
nm_system_restart_mdns_responder ();
}
static void
schedule_activate_check (NMPolicy *policy, NMDevice *device)
{
ActivateData *data;
GSList *iter;
gboolean wireless_enabled;
if (nm_manager_get_state (policy->manager) == NM_STATE_ASLEEP)
return;
// FIXME: kind of a hack, but devices don't have access to the manager
// object directly
wireless_enabled = nm_manager_wireless_enabled (policy->manager);
if (!nm_device_can_activate (device, wireless_enabled))
return;
for (iter = policy->pending_activation_checks; iter; iter = g_slist_next (iter)) {
/* Only one pending activation check at a time */
if (((ActivateData *) iter->data)->device == device)
return;
}
data = g_malloc0 (sizeof (ActivateData));
g_return_if_fail (data != NULL);
data->policy = policy;
data->device = g_object_ref (device);
data->id = g_idle_add (auto_activate_device, data);
policy->pending_activation_checks = g_slist_append (policy->pending_activation_checks, data);
}
static NMConnection *
get_device_connection (NMDevice *device)
{
NMActRequest *req;
req = nm_device_get_act_request (device);
if (!req)
return NULL;
return nm_act_request_get_connection (req);
}
static void
device_state_changed (NMDevice *device, NMDeviceState state, gpointer user_data)
{
NMPolicy *policy = (NMPolicy *) user_data;
NMConnection *connection = get_device_connection (device);
if ((state == NM_DEVICE_STATE_FAILED) || (state == NM_DEVICE_STATE_CANCELLED)) {
/* Mark the connection invalid so it doesn't get automatically chosen */
if (connection) {
g_object_set_data (G_OBJECT (connection), INVALID_TAG, GUINT_TO_POINTER (TRUE));
nm_info ("Marking connection '%s' invalid.", get_connection_id (connection));
}
if (state == NM_DEVICE_STATE_CANCELLED)
schedule_activate_check (policy, device);
} else if (state == NM_DEVICE_STATE_ACTIVATED) {
/* Clear the invalid tag on the connection */
if (connection)
g_object_set_data (G_OBJECT (connection), INVALID_TAG, NULL);
update_routing_and_dns (policy, FALSE);
} else if (state == NM_DEVICE_STATE_DISCONNECTED) {
update_routing_and_dns (policy, FALSE);
schedule_activate_check (policy, device);
}
}
static void
device_carrier_changed (NMDevice8023Ethernet *device,
GParamSpec *pspec,
gpointer user_data)
{
const char *prop = g_param_spec_get_name (pspec);
g_return_if_fail (strcmp (prop, NM_DEVICE_802_3_ETHERNET_CARRIER) == 0);
if (!nm_device_802_3_ethernet_get_carrier (device))
nm_device_interface_deactivate (NM_DEVICE_INTERFACE (device));
else
schedule_activate_check ((NMPolicy *) user_data, NM_DEVICE (device));
}
static void
device_ip4_config_changed (NMDevice *device,
GParamSpec *pspec,
gpointer user_data)
{
update_routing_and_dns ((NMPolicy *) user_data, TRUE);
}
static void
wireless_networks_changed (NMDevice80211Wireless *device, NMAccessPoint *ap, gpointer user_data)
{
schedule_activate_check ((NMPolicy *) user_data, NM_DEVICE (device));
}
typedef struct {
gulong id;
NMDevice *device;
} DeviceSignalID;
static GSList *
add_device_signal_id (GSList *list, gulong id, NMDevice *device)
{
DeviceSignalID *data;
data = g_malloc0 (sizeof (DeviceSignalID));
if (!data)
return list;
data->id = id;
data->device = device;
return g_slist_append (list, data);
}
static void
device_added (NMManager *manager, NMDevice *device, gpointer user_data)
{
NMPolicy *policy = (NMPolicy *) user_data;
gulong id;
id = g_signal_connect (device, "state-changed",
G_CALLBACK (device_state_changed),
policy);
policy->dev_signal_ids = add_device_signal_id (policy->dev_signal_ids, id, device);
id = g_signal_connect (device, "notify::" NM_DEVICE_INTERFACE_IP4_CONFIG,
G_CALLBACK (device_ip4_config_changed),
policy);
policy->dev_signal_ids = add_device_signal_id (policy->dev_signal_ids, id, device);
if (NM_IS_DEVICE_802_11_WIRELESS (device)) {
id = g_signal_connect (device, "access-point-added",
G_CALLBACK (wireless_networks_changed),
policy);
policy->dev_signal_ids = add_device_signal_id (policy->dev_signal_ids, id, device);
id = g_signal_connect (device, "access-point-removed",
G_CALLBACK (wireless_networks_changed),
policy);
policy->dev_signal_ids = add_device_signal_id (policy->dev_signal_ids, id, device);
}
if (NM_IS_DEVICE_802_3_ETHERNET (device)) {
id = g_signal_connect (device, "notify::" NM_DEVICE_802_3_ETHERNET_CARRIER,
G_CALLBACK (device_carrier_changed),
policy);
policy->dev_signal_ids = add_device_signal_id (policy->dev_signal_ids, id, device);
}
schedule_activate_check (policy, device);
}
static void
device_removed (NMManager *manager, NMDevice *device, gpointer user_data)
{
NMPolicy *policy = (NMPolicy *) user_data;
GSList *iter = policy->dev_signal_ids;
/* Clear any signal handlers for this device */
while (iter) {
DeviceSignalID *data = (DeviceSignalID *) iter->data;
GSList *next = g_slist_next (iter);
if (data->device == device) {
policy->dev_signal_ids = g_slist_remove_link (policy->dev_signal_ids, iter);
g_signal_handler_disconnect (data->device, data->id);
g_free (data);
g_slist_free (iter);
}
iter = next;
}
update_routing_and_dns (policy, FALSE);
}
static void
schedule_activate_all (NMPolicy *policy)
{
GSList *iter, *devices;
devices = nm_manager_get_devices (policy->manager);
for (iter = devices; iter; iter = g_slist_next (iter))
schedule_activate_check (policy, NM_DEVICE (iter->data));
}
static void
connections_added (NMManager *manager,
NMConnectionScope scope,
gpointer user_data)
{
schedule_activate_all ((NMPolicy *) user_data);
}
static void
connection_added (NMManager *manager,
NMConnection *connection,
NMConnectionScope scope,
gpointer user_data)
{
schedule_activate_all ((NMPolicy *) user_data);
}
static void
connection_updated (NMManager *manager,
NMConnection *connection,
NMConnectionScope scope,
gpointer user_data)
{
/* Clear the invalid tag on the connection if it got updated. */
g_object_set_data (G_OBJECT (connection), INVALID_TAG, NULL);
schedule_activate_all ((NMPolicy *) user_data);
}
static void
connection_removed (NMManager *manager,
NMConnection *connection,
NMConnectionScope scope,
gpointer user_data)
{
NMSettingConnection *s_con;
GPtrArray *list;
int i;
s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION));
if (!s_con)
return;
list = nm_manager_get_active_connections_by_connection (manager, connection);
if (!list)
return;
for (i = 0; i < list->len; i++) {
char *path = g_ptr_array_index (list, i);
GError *error = NULL;
if (!nm_manager_deactivate_connection (manager, path, &error)) {
nm_warning ("Connection '%s' disappeared, but error deactivating it: (%d) %s",
s_con->id, error->code, error->message);
g_error_free (error);
}
g_free (path);
}
g_ptr_array_free (list, TRUE);
}
NMPolicy *
nm_policy_new (NMManager *manager)
{
NMPolicy *policy;
static gboolean initialized = FALSE;
gulong id;
g_return_val_if_fail (NM_IS_MANAGER (manager), NULL);
g_return_val_if_fail (initialized == FALSE, NULL);
policy = g_malloc0 (sizeof (NMPolicy));
policy->manager = g_object_ref (manager);
policy->update_state_id = 0;
id = g_signal_connect (manager, "state-changed",
G_CALLBACK (global_state_changed), policy);
policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
id = g_signal_connect (manager, "device-added",
G_CALLBACK (device_added), policy);
policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
id = g_signal_connect (manager, "device-removed",
G_CALLBACK (device_removed), policy);
policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
/* Large batch of connections added, manager doesn't want us to
* process each one individually.
*/
id = g_signal_connect (manager, "connections-added",
G_CALLBACK (connections_added), policy);
policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
/* Single connection added */
id = g_signal_connect (manager, "connection-added",
G_CALLBACK (connection_added), policy);
policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
id = g_signal_connect (manager, "connection-updated",
G_CALLBACK (connection_updated), policy);
policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
id = g_signal_connect (manager, "connection-removed",
G_CALLBACK (connection_removed), policy);
policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
return policy;
}
void
nm_policy_destroy (NMPolicy *policy)
{
GSList *iter;
g_return_if_fail (policy != NULL);
for (iter = policy->pending_activation_checks; iter; iter = g_slist_next (iter)) {
ActivateData *data = (ActivateData *) iter->data;
g_source_remove (data->id);
g_object_unref (data->device);
g_free (data);
}
g_slist_free (policy->pending_activation_checks);
for (iter = policy->signal_ids; iter; iter = g_slist_next (iter))
g_signal_handler_disconnect (policy->manager, (gulong) iter->data);
g_slist_free (policy->signal_ids);
for (iter = policy->dev_signal_ids; iter; iter = g_slist_next (iter)) {
DeviceSignalID *data = (DeviceSignalID *) iter->data;
g_signal_handler_disconnect (data->device, data->id);
g_free (data);
}
g_slist_free (policy->dev_signal_ids);
g_object_unref (policy->manager);
g_free (policy);
}