
* src/NetworkManagerPolicy.c - (nm_policy_device_change_check): print out connection name where possible git-svn-id: http://svn-archive.gnome.org/svn/NetworkManager/trunk@2954 4912f4e0-d625-0410-9fb7-b9a5a253dbdc
605 lines
18 KiB
C
605 lines
18 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 "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-802-11-wireless.h"
|
|
#include "nm-device-802-3-ethernet.h"
|
|
#include "nm-dbus-manager.h"
|
|
#include "nm-setting.h"
|
|
|
|
struct NMPolicy {
|
|
NMManager *manager;
|
|
guint device_state_changed_idle_id;
|
|
};
|
|
|
|
static void schedule_change_check (NMPolicy *policy);
|
|
|
|
/* NMPolicy is supposed to be one of the highest classes of the
|
|
NM class hierarchy and the only public API it needs is:
|
|
NMPolicy *nm_policy_new (NMManager *manager);
|
|
void nm_policy_destroy (NMPolicy *policy);
|
|
|
|
Until this hasn't fixed, keep the global policy around.
|
|
*/
|
|
static NMPolicy *global_policy;
|
|
|
|
|
|
static const char *
|
|
get_connection_name (NMConnection *connection)
|
|
{
|
|
NMSettingConnection *s_con;
|
|
|
|
g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
|
|
|
|
s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_SETTING_CONNECTION);
|
|
g_return_val_if_fail (s_con != NULL, NULL);
|
|
|
|
return s_con->name;
|
|
}
|
|
|
|
/*
|
|
* nm_policy_auto_get_best_device
|
|
*
|
|
* Find the best device to use, regardless of whether we are
|
|
* "locked" on one device at this time.
|
|
*
|
|
*/
|
|
static NMDevice *
|
|
nm_policy_auto_get_best_device (NMPolicy *policy,
|
|
NMConnection **connection,
|
|
char **specific_object)
|
|
{
|
|
GSList *connections;
|
|
GSList * elt;
|
|
NMDevice8023Ethernet * best_wired_dev = NULL;
|
|
guint best_wired_prio = 0;
|
|
NMConnection * best_wired_connection = NULL;
|
|
char * best_wired_specific_object = NULL;
|
|
NMDevice80211Wireless * best_wireless_dev = NULL;
|
|
guint best_wireless_prio = 0;
|
|
NMConnection * best_wireless_connection = NULL;
|
|
char * best_wireless_specific_object = NULL;
|
|
NMDevice * highest_priority_dev = NULL;
|
|
|
|
g_return_val_if_fail (connection != NULL, NULL);
|
|
g_return_val_if_fail (*connection == NULL, NULL);
|
|
g_return_val_if_fail (specific_object != NULL, NULL);
|
|
g_return_val_if_fail (*specific_object == NULL, NULL);
|
|
|
|
if (nm_manager_get_state (policy->manager) == NM_STATE_ASLEEP)
|
|
return NULL;
|
|
|
|
/* System connections first, then user connections */
|
|
connections = nm_manager_get_connections (policy->manager, NM_CONNECTION_TYPE_SYSTEM);
|
|
connections = g_slist_concat (connections, nm_manager_get_connections (policy->manager, NM_CONNECTION_TYPE_USER));
|
|
|
|
for (elt = nm_manager_get_devices (policy->manager); elt; elt = elt->next) {
|
|
NMConnection *tmp_con = NULL;
|
|
char *tmp_obj = NULL;
|
|
gboolean link_active;
|
|
guint prio = 0;
|
|
NMDevice * dev = (NMDevice *)(elt->data);
|
|
guint32 caps;
|
|
|
|
link_active = nm_device_has_active_link (dev);
|
|
caps = nm_device_get_capabilities (dev);
|
|
|
|
tmp_con = nm_device_get_best_connection (dev, connections, &tmp_obj);
|
|
if (tmp_con == NULL) {
|
|
NMActRequest *req = nm_device_get_act_request (dev);
|
|
|
|
/* If the device is activating, the NMConnection it's got is the
|
|
* best one. In other words, follow activation of a particular
|
|
* NMConnection through to success/failure rather than cutting it
|
|
* off if it becomes invalid
|
|
*/
|
|
tmp_con = req ? nm_act_request_get_connection (req) : NULL;
|
|
if (!tmp_con)
|
|
continue;
|
|
}
|
|
|
|
if (NM_IS_DEVICE_802_3_ETHERNET (dev)) {
|
|
if (link_active)
|
|
prio += 1;
|
|
|
|
if (nm_device_get_act_request (dev) && link_active)
|
|
prio += 1;
|
|
|
|
if (prio > best_wired_prio) {
|
|
best_wired_dev = NM_DEVICE_802_3_ETHERNET (dev);
|
|
best_wired_prio = prio;
|
|
best_wired_connection = tmp_con;
|
|
best_wired_specific_object = tmp_obj;
|
|
}
|
|
} else if ( NM_IS_DEVICE_802_11_WIRELESS (dev)
|
|
&& nm_manager_wireless_enabled (policy->manager)) {
|
|
/* Bump by 1 so that _something_ gets chosen every time */
|
|
prio += 1;
|
|
|
|
if (link_active)
|
|
prio += 1;
|
|
|
|
if (nm_device_get_act_request (dev) && link_active)
|
|
prio += 3;
|
|
|
|
if (prio > best_wireless_prio) {
|
|
best_wireless_dev = NM_DEVICE_802_11_WIRELESS (dev);
|
|
best_wireless_prio = prio;
|
|
best_wireless_connection = tmp_con;
|
|
best_wireless_specific_object = tmp_obj;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (best_wired_dev) {
|
|
highest_priority_dev = NM_DEVICE (best_wired_dev);
|
|
*connection = g_object_ref (best_wired_connection);
|
|
*specific_object = best_wired_specific_object;
|
|
} else if (best_wireless_dev) {
|
|
gboolean can_activate;
|
|
|
|
can_activate = nm_device_802_11_wireless_can_activate (best_wireless_dev);
|
|
if (can_activate) {
|
|
highest_priority_dev = NM_DEVICE (best_wireless_dev);
|
|
*connection = g_object_ref (best_wireless_connection);
|
|
*specific_object = best_wireless_specific_object;
|
|
}
|
|
}
|
|
|
|
g_slist_foreach (connections, (GFunc) g_object_unref, NULL);
|
|
g_slist_free (connections);
|
|
|
|
if (FALSE) {
|
|
nm_info ("AUTO: Best wired device = %s, best wireless device = %s, best connection name = '%s'",
|
|
best_wired_dev ? nm_device_get_iface (NM_DEVICE (best_wired_dev)) : "(null)",
|
|
best_wireless_dev ? nm_device_get_iface (NM_DEVICE (best_wireless_dev)) : "(null)",
|
|
*connection ? get_connection_name (*connection) : "(none)");
|
|
}
|
|
|
|
return *connection ? highest_priority_dev : NULL;
|
|
}
|
|
|
|
/*
|
|
* nm_policy_device_change_check
|
|
*
|
|
* Figures out which interface to switch the active
|
|
* network connection to if our global network state has changed.
|
|
* Global network state changes are triggered by:
|
|
* 1) insertion/deletion of interfaces
|
|
* 2) link state change of an interface
|
|
* 3) wireless network topology changes
|
|
*
|
|
*/
|
|
static gboolean
|
|
nm_policy_device_change_check (gpointer user_data)
|
|
{
|
|
NMPolicy *policy = (NMPolicy *) user_data;
|
|
GSList *iter;
|
|
guint32 caps;
|
|
NMConnection *connection = NULL;
|
|
NMConnection *old_connection = NULL;
|
|
NMActRequest *old_act_req = NULL;
|
|
char * specific_object = NULL;
|
|
NMDevice * new_dev = NULL;
|
|
NMDevice * old_dev = NULL;
|
|
gboolean do_switch = FALSE;
|
|
|
|
switch (nm_manager_get_state (policy->manager)) {
|
|
case NM_STATE_CONNECTED:
|
|
old_dev = nm_manager_get_active_device (policy->manager);
|
|
caps = nm_device_get_capabilities (old_dev);
|
|
|
|
/* Don't interrupt semi-supported devices. If the user chose
|
|
* one, they must explicitly choose to move to another device, we're not
|
|
* going to move for them.
|
|
*/
|
|
if ((NM_IS_DEVICE_802_3_ETHERNET (old_dev) && !(caps & NM_DEVICE_CAP_CARRIER_DETECT))) {
|
|
nm_info ("Old device '%s' was semi-supported and user chosen, won't"
|
|
" change unless told to.",
|
|
nm_device_get_iface (old_dev));
|
|
goto out;
|
|
}
|
|
break;
|
|
case NM_STATE_CONNECTING:
|
|
for (iter = nm_manager_get_devices (policy->manager); iter; iter = iter->next) {
|
|
NMDevice *d = NM_DEVICE (iter->data);
|
|
|
|
if (nm_device_is_activating (d)) {
|
|
if (nm_device_can_interrupt_activation (d)) {
|
|
old_dev = d;
|
|
break;
|
|
} else
|
|
goto out;
|
|
}
|
|
}
|
|
break;
|
|
case NM_STATE_DISCONNECTED:
|
|
if (nm_manager_activation_pending (policy->manager)) {
|
|
nm_info ("There is a pending activation, won't change.");
|
|
goto out;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
new_dev = nm_policy_auto_get_best_device (policy, &connection, &specific_object);
|
|
|
|
if (old_dev) {
|
|
old_act_req = nm_device_get_act_request (old_dev);
|
|
if (old_act_req)
|
|
old_connection = nm_act_request_get_connection (old_act_req);
|
|
}
|
|
|
|
/* Four cases here:
|
|
*
|
|
* 1) old device is NULL, new device is NULL - we aren't currently connected to anything, and we
|
|
* can't find anything to connect to. Do nothing.
|
|
*
|
|
* 2) old device is NULL, new device is good - we aren't currently connected to anything, but
|
|
* we have something we can connect to. Connect to it.
|
|
*
|
|
* 3) old device is good, new device is NULL - have a current connection, but it's no good since
|
|
* auto device picking didn't come up with the save device. Terminate current connection.
|
|
*
|
|
* 4) old device is good, new device is good - have a current connection, and auto device picking
|
|
* came up with a device too. More considerations:
|
|
* a) different devices? activate new device
|
|
* b) same device, different access points? activate new device
|
|
* c) same device, same access point? do nothing
|
|
*/
|
|
|
|
if (!old_dev && !new_dev) {
|
|
; /* Do nothing, wait for something like link-state to change, or an access point to be found */
|
|
} else if (!old_dev && new_dev) {
|
|
/* Activate new device */
|
|
nm_info ("SWITCH: no current connection, found better connection '%s (%s)'.",
|
|
connection ? get_connection_name (connection) : "(none)",
|
|
nm_device_get_iface (new_dev));
|
|
do_switch = TRUE;
|
|
} else if (old_dev && !new_dev) {
|
|
/* Terminate current connection */
|
|
nm_info ("SWITCH: terminating current connection '%s (%s)' because it's"
|
|
" no longer valid.",
|
|
old_connection ? get_connection_name (old_connection) : "(none)",
|
|
nm_device_get_iface (old_dev));
|
|
do_switch = TRUE;
|
|
} else if (old_dev && new_dev) {
|
|
gboolean old_user_requested = nm_act_request_get_user_requested (old_act_req);
|
|
gboolean old_has_link = nm_device_has_active_link (old_dev);
|
|
|
|
if (NM_IS_DEVICE_802_3_ETHERNET (old_dev)) {
|
|
/* Only switch if the old device was not user requested, and we are switching to
|
|
* a new device. Note that new_dev will never be wireless since automatic device picking
|
|
* above will prefer a wired device to a wireless device.
|
|
*/
|
|
if ((!old_user_requested || !old_has_link) && (new_dev != old_dev)) {
|
|
nm_info ("SWITCH: found better connection '%s (%s)' than "
|
|
" current connection '%s (%s)'.",
|
|
connection ? get_connection_name (connection) : "(none)",
|
|
nm_device_get_iface (new_dev),
|
|
old_connection ? get_connection_name (old_connection) : "(none)",
|
|
nm_device_get_iface (old_dev));
|
|
do_switch = TRUE;
|
|
}
|
|
} else if (NM_IS_DEVICE_802_11_WIRELESS (old_dev)) {
|
|
/* Only switch if the old device's wireless config is invalid */
|
|
if (NM_IS_DEVICE_802_11_WIRELESS (new_dev)) {
|
|
NMAccessPoint *old_ap = nm_device_802_11_wireless_get_activation_ap (NM_DEVICE_802_11_WIRELESS (old_dev));
|
|
int old_mode = nm_ap_get_mode (old_ap);
|
|
gboolean same_activating = FALSE;
|
|
|
|
/* Don't interrupt activation of a wireless device by
|
|
* trying to auto-activate any connection on that device.
|
|
*/
|
|
if (old_dev == new_dev && nm_device_is_activating (new_dev))
|
|
same_activating = TRUE;
|
|
|
|
if (!same_activating && !old_has_link && (old_mode != IW_MODE_ADHOC)) {
|
|
NMSettingConnection * new_sc = (NMSettingConnection *) nm_connection_get_setting (connection, NM_SETTING_CONNECTION);
|
|
NMSettingConnection * old_sc = (NMSettingConnection *) nm_connection_get_setting (old_connection, NM_SETTING_CONNECTION);
|
|
|
|
nm_info ("SWITCH: found better connection '%s/%s'"
|
|
" than current connection '%s/%s'. "
|
|
"have_link=%d",
|
|
nm_device_get_iface (new_dev),
|
|
new_sc->name,
|
|
nm_device_get_iface (old_dev),
|
|
old_sc->name,
|
|
old_has_link);
|
|
do_switch = TRUE;
|
|
}
|
|
} else if (NM_IS_DEVICE_802_3_ETHERNET (new_dev)) {
|
|
/* Always prefer Ethernet over wireless, unless the user explicitly switched away. */
|
|
if (!old_user_requested)
|
|
do_switch = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (do_switch) {
|
|
// FIXME: remove old_dev deactivation when multiple device support lands
|
|
if (old_dev)
|
|
nm_device_interface_deactivate (NM_DEVICE_INTERFACE (old_dev));
|
|
|
|
if (new_dev)
|
|
nm_manager_activate_device (policy->manager, new_dev, connection, specific_object, FALSE);
|
|
}
|
|
|
|
out:
|
|
if (connection)
|
|
g_object_unref (connection);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
device_change_check_done (gpointer user_data)
|
|
{
|
|
NMPolicy *policy = (NMPolicy *) user_data;
|
|
policy->device_state_changed_idle_id = 0;
|
|
}
|
|
|
|
static void
|
|
schedule_change_check (NMPolicy *policy)
|
|
{
|
|
if (policy->device_state_changed_idle_id > 0)
|
|
return;
|
|
|
|
policy->device_state_changed_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
|
|
nm_policy_device_change_check,
|
|
policy,
|
|
device_change_check_done);
|
|
}
|
|
|
|
/* FIXME: remove when multiple active device support has landed */
|
|
static void
|
|
deactivate_old_device (NMPolicy *policy, NMDevice *new_device)
|
|
{
|
|
GSList *iter;
|
|
|
|
for (iter = nm_manager_get_devices (policy->manager); iter; iter = iter->next) {
|
|
NMDevice *dev = NM_DEVICE (iter->data);
|
|
|
|
if (dev == new_device)
|
|
continue;
|
|
|
|
switch (nm_device_get_state (dev)) {
|
|
case NM_DEVICE_STATE_PREPARE:
|
|
case NM_DEVICE_STATE_CONFIG:
|
|
case NM_DEVICE_STATE_NEED_AUTH:
|
|
case NM_DEVICE_STATE_IP_CONFIG:
|
|
case NM_DEVICE_STATE_ACTIVATED:
|
|
nm_device_interface_deactivate (NM_DEVICE_INTERFACE (dev));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
device_state_changed (NMDevice *device, NMDeviceState state, gpointer user_data)
|
|
{
|
|
NMPolicy *policy = (NMPolicy *) user_data;
|
|
|
|
if (state == NM_DEVICE_STATE_PREPARE)
|
|
deactivate_old_device (policy, device); /* FIXME: remove when multiple active device support has landed */
|
|
|
|
else if (state == NM_DEVICE_STATE_FAILED || state == NM_DEVICE_STATE_CANCELLED)
|
|
schedule_change_check (policy);
|
|
}
|
|
|
|
static void
|
|
device_carrier_changed (NMDevice *device, gboolean carrier_on, gpointer user_data)
|
|
{
|
|
NMPolicy *policy = (NMPolicy *) user_data;
|
|
|
|
schedule_change_check (policy);
|
|
}
|
|
|
|
static void
|
|
wireless_networks_changed (NMDevice80211Wireless *device, NMAccessPoint *ap, gpointer user_data)
|
|
{
|
|
NMPolicy *policy = (NMPolicy *) user_data;
|
|
|
|
schedule_change_check (policy);
|
|
}
|
|
|
|
static void
|
|
device_added (NMManager *manager, NMDevice *device, gpointer user_data)
|
|
{
|
|
NMPolicy *policy = (NMPolicy *) user_data;
|
|
|
|
g_signal_connect (device, "state-changed",
|
|
G_CALLBACK (device_state_changed),
|
|
policy);
|
|
|
|
g_signal_connect (device, "carrier-changed",
|
|
G_CALLBACK (device_carrier_changed),
|
|
policy);
|
|
|
|
if (NM_IS_DEVICE_802_11_WIRELESS (device)) {
|
|
g_signal_connect (device, "access-point-added",
|
|
G_CALLBACK (wireless_networks_changed),
|
|
policy);
|
|
g_signal_connect (device, "access-point-removed",
|
|
G_CALLBACK (wireless_networks_changed),
|
|
policy);
|
|
}
|
|
|
|
schedule_change_check (policy);
|
|
}
|
|
|
|
static void
|
|
device_removed (NMManager *manager, NMDevice *device, gpointer user_data)
|
|
{
|
|
NMPolicy *policy = (NMPolicy *) user_data;
|
|
|
|
schedule_change_check (policy);
|
|
}
|
|
|
|
static void
|
|
state_changed (NMManager *manager, NMState state, gpointer user_data)
|
|
{
|
|
NMPolicy *policy = (NMPolicy *) user_data;
|
|
|
|
if (state == NM_STATE_CONNECTING) {
|
|
/* A device starts activation, bring all devices down
|
|
* Remove this when we support multiple active devices.
|
|
*/
|
|
|
|
NMDevice *old_dev;
|
|
|
|
if ((old_dev = nm_manager_get_active_device (policy->manager)))
|
|
nm_device_interface_deactivate (NM_DEVICE_INTERFACE (old_dev));
|
|
}
|
|
}
|
|
|
|
static void
|
|
connections_added (NMManager *manager,
|
|
NMConnectionType connection_type,
|
|
gpointer user_data)
|
|
{
|
|
NMPolicy *policy = (NMPolicy *) user_data;
|
|
|
|
schedule_change_check (policy);
|
|
}
|
|
|
|
static void
|
|
connection_added (NMManager *manager,
|
|
NMConnection *connection,
|
|
NMConnectionType connection_type,
|
|
gpointer user_data)
|
|
{
|
|
NMPolicy *policy = (NMPolicy *) user_data;
|
|
|
|
schedule_change_check (policy);
|
|
}
|
|
|
|
static void
|
|
connection_updated (NMManager *manager,
|
|
NMConnection *connection,
|
|
NMConnectionType connection_type,
|
|
gpointer user_data)
|
|
{
|
|
NMPolicy *policy = (NMPolicy *) user_data;
|
|
|
|
schedule_change_check (policy);
|
|
}
|
|
|
|
static void
|
|
connection_removed (NMManager *manager,
|
|
NMConnection *connection,
|
|
NMConnectionType connection_type,
|
|
gpointer user_data)
|
|
{
|
|
NMPolicy *policy = (NMPolicy *) user_data;
|
|
GSList *iter;
|
|
|
|
/* If the connection just removed was active, deactive it */
|
|
for (iter = nm_manager_get_devices (manager); iter; iter = g_slist_next (iter)) {
|
|
NMDevice *device = (NMDevice *) iter->data;
|
|
NMActRequest *req = nm_device_get_act_request (device);
|
|
NMConnection *dev_connection;
|
|
|
|
if (!req)
|
|
continue;
|
|
|
|
dev_connection = nm_act_request_get_connection (req);
|
|
if (dev_connection == connection)
|
|
nm_device_interface_deactivate (NM_DEVICE_INTERFACE (device));
|
|
}
|
|
|
|
schedule_change_check (policy);
|
|
}
|
|
|
|
NMPolicy *
|
|
nm_policy_new (NMManager *manager)
|
|
{
|
|
NMPolicy *policy;
|
|
|
|
g_return_val_if_fail (NM_IS_MANAGER (manager), NULL);
|
|
|
|
g_assert (global_policy == NULL);
|
|
|
|
policy = g_slice_new (NMPolicy);
|
|
policy->manager = g_object_ref (manager);
|
|
|
|
g_signal_connect (manager, "device-added",
|
|
G_CALLBACK (device_added), policy);
|
|
|
|
g_signal_connect (manager, "device-removed",
|
|
G_CALLBACK (device_removed), policy);
|
|
|
|
g_signal_connect (manager, "state-change",
|
|
G_CALLBACK (state_changed), policy);
|
|
|
|
/* Large batch of connections added, manager doesn't want us to
|
|
* process each one individually.
|
|
*/
|
|
g_signal_connect (manager, "connections-added",
|
|
G_CALLBACK (connections_added), policy);
|
|
|
|
/* Single connection added */
|
|
g_signal_connect (manager, "connection-added",
|
|
G_CALLBACK (connection_added), policy);
|
|
|
|
g_signal_connect (manager, "connection-updated",
|
|
G_CALLBACK (connection_updated), policy);
|
|
|
|
g_signal_connect (manager, "connection-removed",
|
|
G_CALLBACK (connection_removed), policy);
|
|
|
|
global_policy = policy;
|
|
|
|
return policy;
|
|
}
|
|
|
|
void
|
|
nm_policy_destroy (NMPolicy *policy)
|
|
{
|
|
if (policy) {
|
|
g_object_unref (policy->manager);
|
|
g_slice_free (NMPolicy, policy);
|
|
}
|
|
|
|
global_policy = NULL;
|
|
}
|
|
|