NetworkManager/src/NetworkManagerPolicy.c
Dan Williams b7df56d512 2007-08-12 Dan Williams <dcbw@redhat.com>
* src/NetworkManagerPolicy.c
		- (nm_policy_new): hook up to connection-added / connection-removed
			signals instead of connections-changed



git-svn-id: http://svn-archive.gnome.org/svn/NetworkManager/trunk@2667 4912f4e0-d625-0410-9fb7-b9a5a253dbdc
2007-08-13 01:45:47 +00:00

661 lines
19 KiB
C

/* 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 "NetworkManagerAPList.h"
#include "NetworkManagerDbus.h"
#include "nm-activation-request.h"
#include "nm-utils.h"
#include "nm-dbus-nmi.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"
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;
/*
* 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, NMAccessPoint **ap)
{
GSList * elt;
NMDevice8023Ethernet * best_wired_dev = NULL;
guint best_wired_prio = 0;
NMDevice80211Wireless * best_wireless_dev = NULL;
guint best_wireless_prio = 0;
NMDevice * highest_priority_dev = NULL;
g_return_val_if_fail (ap != NULL, NULL);
if (nm_manager_get_state (policy->manager) == NM_STATE_ASLEEP)
return NULL;
for (elt = nm_manager_get_devices (policy->manager); elt; elt = elt->next) {
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);
/* Don't use devices that SUCK */
if (!(caps & NM_DEVICE_CAP_NM_SUPPORTED))
continue;
if (NM_IS_DEVICE_802_3_ETHERNET (dev)) {
/* We never automatically choose devices that don't support carrier detect */
if (!(caps & NM_DEVICE_CAP_CARRIER_DETECT))
continue;
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;
}
}
else if (NM_IS_DEVICE_802_11_WIRELESS (dev) &&
nm_manager_wireless_enabled (policy->manager)) {
/* Don't automatically choose a device that doesn't support wireless scanning */
if (!(caps & NM_DEVICE_CAP_WIRELESS_SCAN))
continue;
/* 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;
}
}
}
if (best_wired_dev)
highest_priority_dev = NM_DEVICE (best_wired_dev);
else if (best_wireless_dev)
{
gboolean can_activate;
can_activate = nm_device_802_11_wireless_can_activate (best_wireless_dev);
*ap = nm_device_802_11_wireless_get_best_ap (best_wireless_dev);
/* If the device doesn't have a "best" ap, then we can't use it */
if (!*ap)
highest_priority_dev = NULL;
else if (can_activate == TRUE)
highest_priority_dev = NM_DEVICE (best_wireless_dev);
}
if (FALSE) {
const GByteArray * ssid = (best_wireless_dev && *ap) ? nm_ap_get_ssid (*ap) : NULL;
nm_info ("AUTO: Best wired device = %s, best wireless device = %s (%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)",
ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "null" );
}
return highest_priority_dev;
}
static NMConnection *
create_connection (NMDevice *device, NMAccessPoint *ap)
{
NMConnection *connection = NULL;
NMSetting *setting = NULL;
if (NM_IS_DEVICE_802_3_ETHERNET (device)) {
nm_info ("Will activate connection '%s'.", nm_device_get_iface (device));
setting = nm_setting_wired_new ();
} else if (NM_IS_DEVICE_802_11_WIRELESS (device) && ap) {
NMSettingWireless *wireless;
const GByteArray * ssid = nm_ap_get_ssid (ap);
nm_info ("Will activate connection '%s/%s'.",
nm_device_get_iface (device),
ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)");
setting = nm_setting_wireless_new ();
wireless = (NMSettingWireless *) setting;
wireless->ssid = g_byte_array_sized_new (ssid->len);
g_byte_array_append (wireless->ssid, ssid->data, ssid->len);
wireless->mode = g_strdup ("infrastructure");
} else {
nm_warning ("Unhandled device type '%s'", G_OBJECT_CLASS_NAME (device));
}
if (setting) {
NMSettingInfo *info;
connection = nm_connection_new ();
nm_connection_add_setting (connection, setting);
info = (NMSettingInfo *) nm_setting_info_new ();
info->name = g_strdup ("Auto");
info->devtype = g_strdup (setting->name);
nm_connection_add_setting (connection, (NMSetting *) info);
}
return connection;
}
/*
* 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;
NMAccessPoint * ap = 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);
break;
case NM_STATE_CONNECTING:
for (iter = nm_manager_get_devices (policy->manager); iter; iter = iter->next) {
if (nm_device_is_activating (NM_DEVICE (iter->data))) {
old_dev = NM_DEVICE (iter->data);
break;
}
}
break;
default:
break;
}
if (old_dev) {
guint32 caps = nm_device_get_capabilities (old_dev);
/* Don't interrupt a currently activating device. */
if ( nm_device_is_activating (old_dev)
&& !nm_device_can_interrupt_activation (old_dev)) {
nm_info ("Old device '%s' activating, won't change.", nm_device_get_iface (old_dev));
goto out;
}
/* Don't interrupt semi-supported devices either. 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_IS_DEVICE_802_11_WIRELESS (old_dev) && !(caps & NM_DEVICE_CAP_WIRELESS_SCAN))) {
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;
}
}
new_dev = nm_policy_auto_get_best_device (policy, &ap);
/* 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'.", 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' because it's no longer valid.", nm_device_get_iface (old_dev));
nm_device_interface_deactivate (NM_DEVICE_INTERFACE (old_dev));
do_switch = TRUE;
} else if (old_dev && new_dev) {
NMActRequest * old_act_req = nm_device_get_act_request (old_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' than current "
"connection '%s'.",
nm_device_get_iface (new_dev),
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));
const GByteArray * old_ssid = nm_ap_get_ssid (old_ap);
int old_mode = nm_ap_get_mode (old_ap);
const GByteArray * new_ssid = nm_ap_get_ssid (ap);
gboolean same_request = FALSE;
/* Schedule new activation if the currently associated
* access point is not the "best" one or we've lost the
* link to the old access point. We don't switch away
* from Ad-Hoc APs either.
*/
gboolean same_ssid = nm_utils_same_ssid (old_ssid, new_ssid);
/* If the "best" AP's SSID is the same as the current activation
* request's SSID, but the current activation request isn't
* done yet, don't switch. This prevents multiple requests for the
* AP's password on startup.
*/
if ((old_dev == new_dev) && nm_device_is_activating (new_dev) && same_ssid)
same_request = TRUE;
if (!same_request && (!same_ssid || !old_has_link) && (old_mode != IW_MODE_ADHOC)) {
char * new_esc_ssid;
char * old_esc_ssid;
new_esc_ssid = g_strdup (new_ssid ? nm_utils_escape_ssid (new_ssid->data, new_ssid->len) : "(none)");
old_esc_ssid = g_strdup (old_ssid ? nm_utils_escape_ssid (old_ssid->data, old_ssid->len) : "(none)");
nm_info ("SWITCH: found better connection '%s/%s'"
" than current connection '%s/%s'. "
"same_ssid=%d, have_link=%d",
nm_device_get_iface (new_dev),
new_esc_ssid,
nm_device_get_iface (old_dev),
old_esc_ssid,
same_ssid, old_has_link);
g_free (new_esc_ssid);
g_free (old_esc_ssid);
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 && new_dev) {
NMConnection *connection;
connection = create_connection (new_dev, ap);
if (connection)
nm_device_interface_activate (NM_DEVICE_INTERFACE (new_dev),
connection, FALSE);
}
if (ap)
g_object_unref (ap);
out:
return FALSE;
}
static guint allowed_list_update_id = 0;
/*
* nm_policy_allowed_ap_list_update
*
* Requery NetworkManagerInfo for a list of updated
* allowed wireless networks.
*
*/
static gboolean
nm_policy_allowed_ap_list_update (gpointer user_data)
{
NMData * data = (NMData *)user_data;
allowed_list_update_id = 0;
g_return_val_if_fail (data != NULL, FALSE);
nm_info ("Updating allowed wireless network lists.");
/* Query info daemon for network lists if its now running */
if (data->allowed_ap_list)
nm_ap_list_unref (data->allowed_ap_list);
if ((data->allowed_ap_list = nm_ap_list_new (NETWORK_TYPE_ALLOWED)))
nm_dbus_update_allowed_networks (data->allowed_ap_list, data);
return FALSE;
}
/*
* nm_policy_schedule_allowed_ap_list_update
*
* Schedule an update of the allowed AP list in the main thread.
*
*/
void nm_policy_schedule_allowed_ap_list_update (NMData *app_data)
{
GSource * source;
guint id;
g_return_if_fail (app_data != NULL);
if (allowed_list_update_id > 0)
return;
id = g_idle_add (nm_policy_allowed_ap_list_update, app_data);
allowed_list_update_id = id;
source = g_main_context_find_source_by_id (NULL, id);
if (source) {
g_source_set_priority (source, G_PRIORITY_HIGH_IDLE);
}
}
static gboolean device_list_update_id = 0;
/*
* nm_policy_device_list_update_from_allowed_list
*
* Requery NetworkManagerInfo for a list of updated
* allowed wireless networks.
*
*/
static gboolean
nm_policy_device_list_update_from_allowed_list (gpointer user_data)
{
NMData * data = (NMData *) user_data;
GSList * elt;
device_list_update_id = 0;
g_return_val_if_fail (data != NULL, FALSE);
for (elt = nm_manager_get_devices (global_policy->manager); elt; elt = elt->next) {
NMDevice *dev = (NMDevice *)(elt->data);
NMDevice80211Wireless * wdev;
if (!NM_IS_DEVICE_802_11_WIRELESS (dev))
continue;
wdev = NM_DEVICE_802_11_WIRELESS (dev);
if (nm_device_get_capabilities (dev) & NM_DEVICE_CAP_WIRELESS_SCAN) {
/* Once we have the list, copy in any relevant information from our
* Allowed list and fill in the SSID of base stations that aren't
* broadcasting their SSID, if we have their MAC address in our
* allowed list.
*/
nm_ap_list_copy_ssids_by_address (nm_device_802_11_wireless_ap_list_get (wdev),
data->allowed_ap_list);
nm_ap_list_copy_properties (nm_device_802_11_wireless_ap_list_get (wdev),
data->allowed_ap_list);
} else {
nm_device_802_11_wireless_copy_allowed_to_dev_list (wdev, data->allowed_ap_list);
}
nm_ap_list_remove_duplicate_ssids (nm_device_802_11_wireless_ap_list_get (wdev));
}
schedule_change_check ((gpointer) global_policy);
return FALSE;
}
/*
* nm_policy_schedule_device_ap_lists_update_from_allowed
*
* Schedule an update of each wireless device's AP list from
* the allowed list, in the main thread.
*
*/
void
nm_policy_schedule_device_ap_lists_update_from_allowed (NMData *app_data)
{
g_return_if_fail (app_data != NULL);
if (device_list_update_id > 0)
return;
device_list_update_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
nm_policy_device_list_update_from_allowed_list,
app_data,
NULL);
}
/*****************************************************************************/
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);
}
static void
device_state_changed (NMDevice *device, NMDeviceState state, gpointer user_data)
{
NMPolicy *policy = (NMPolicy *) user_data;
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, "network-added",
G_CALLBACK (wireless_networks_changed),
policy);
g_signal_connect (device, "network-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
connection_added (NMManager *manager,
NMConnection *connection,
gpointer user_data)
{
NMPolicy *policy = (NMPolicy *) user_data;
nm_info ("connection %p added", connection);
}
static void
connection_removed (NMManager *manager,
NMConnection *connection,
gpointer user_data)
{
NMPolicy *policy = (NMPolicy *) user_data;
nm_info ("connection %p removed", connection);
}
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);
g_signal_connect (manager, "connection-added",
G_CALLBACK (connection_added), 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;
}