/* NetworkManager -- Network link manager * * Dan Williams * * 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 #include #include #include #include #include #include #include #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; /* * 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 * 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; 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, &tmp_obj); if (tmp_con == NULL) 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 = 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 = best_wireless_connection; *specific_object = best_wireless_specific_object; } } if (FALSE) { char * con_name = g_strdup ("(none)"); if (*connection) { NMSettingConnection * s_con; s_con = (NMSettingConnection *) nm_connection_get_setting (*connection, "connection"); con_name = g_strdup (s_con->name); } 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)", con_name); g_free (con_name); } 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; NMConnection * connection = 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); 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_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, &connection, &specific_object); /* 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)); 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)) { #if 0 /* 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, TRUE); /* 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; } #endif } } if (do_switch) { if (old_dev) { nm_device_interface_deactivate (NM_DEVICE_INTERFACE (old_dev)); } if (new_dev) { nm_device_interface_activate (NM_DEVICE_INTERFACE (new_dev), connection, specific_object, FALSE); } } out: 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); } 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; schedule_change_check (policy); } static void connection_removed (NMManager *manager, NMConnection *connection, gpointer user_data) { NMPolicy *policy = (NMPolicy *) user_data; 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); 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; }