| # Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import glob |
| import logging |
| import os |
| import subprocess |
| import time |
| |
| # Import WLAN into this module's namespace, since it may be used by |
| # some test lists. |
| import factory_common # pylint: disable=W0611 |
| from cros.factory.utils.net_utils import WLAN # pylint: disable=W0611 |
| |
| try: |
| # pylint: disable=W0611 |
| from cros.factory.goofy import flimflam_test_path |
| import dbus # pylint: disable=F0401 |
| import flimflam # pylint: disable=F0401 |
| except ImportError: |
| # E.g., in chroot |
| pass |
| |
| _CONNECTION_TIMEOUT_SECS = 15.0 |
| _PING_TIMEOUT_SECS = 15 |
| _SLEEP_INTERVAL_SECS = 0.5 |
| _SCAN_INTERVAL_SECS = 10 |
| |
| # The dependency of network manager in current ChromeOS is: |
| # wpasupplicant +-> shill -> shill_respawn |
| # \-> modemmanager |
| # So the right order to stop network is: |
| # shill_respawn -> shill -> wpasupplicant. |
| # modemmanager / |
| |
| _UNKNOWN_PROC = 'unknown' |
| _DEFAULT_MANAGER = 'shill' |
| _DEFAULT_PROC_NAME = 'shill' |
| _MANAGER_LIST = ['flimflam', 'shill'] |
| _PROC_NAME_LIST = [_UNKNOWN_PROC, 'flimflamd', 'shill'] |
| _DEPSERVICE_LIST = ['wpasupplicant'] |
| _SUBSERVICE_LIST = ['shill_respawn', 'modemmanager'] |
| |
| # %s is the network manager process name, i.e. flimflam or shill. |
| _PROFILE_LOCATION = '/var/cache/%s/default.profile' |
| |
| |
| class ConnectionManagerException(Exception): |
| pass |
| |
| |
| def GetBaseNetworkManager(): |
| ''' Wrapper function of the base network manager constructor. |
| |
| The function returns an object of the real underlying ChromeOS network |
| manager (flimflam/shill). Although we are actually using shill right now, |
| the naming in the Python interface has not been changed yet and we still |
| need to use the flimflam module as the interface. The wrapping is to |
| facilitate the writing of unit test and to simplify the migration to the shill |
| later. Please note that this is different from the network_manager parameter |
| used in the ConnectionManager and is determined only by the Python interface |
| provided the OS. |
| ''' |
| return flimflam.FlimFlam() |
| |
| |
| class ConnectionManager(object): |
| |
| def __init__(self, wlans=None, scan_interval=_SCAN_INTERVAL_SECS, |
| network_manager=_DEFAULT_MANAGER, |
| process_name=_DEFAULT_PROC_NAME, |
| start_enabled=True, |
| depservices=list(_DEPSERVICE_LIST), |
| subservices=list(_SUBSERVICE_LIST), |
| profile_path=_PROFILE_LOCATION, |
| override_blacklisted_devices=None): |
| '''Constructor. |
| |
| Args: |
| wlans: A list of preferred wireless networks and their properties. |
| Each item should be a WLAN object. |
| scan_interval: The desired interval between each wireless network scanning |
| in seconds. Setting this value to 0 disables periodic |
| scanning. |
| network_manager: The name of the network manager in initctl. It |
| should be either flimflam(old) or shill(new). |
| process_name: The name of the network manager process, which should be |
| flimflamd or shill. If you are not sure about it, you can |
| use _UNKNOWN_PROC to let the class auto-detect it. |
| start_enabled: Whether networking should start enabled. |
| depservices: The list of networking-related system services that flimflam/ |
| shill depends on. |
| subservices: The list of networking-related system services other than |
| flimflam/shill and their dependency. |
| profile_path: The file path of the network profile used by flimflam/shill. |
| override_blacklisted_devices: Blacklist to override shill's default |
| settings. Should be a list of strings |
| (like ['eth0', 'wlan0']), an empty list or |
| empty string (block nothing), or None |
| (don't override). |
| ''' |
| # Black hole for those useless outputs. |
| self.fnull = open(os.devnull, 'w') |
| |
| assert network_manager in _MANAGER_LIST |
| assert process_name in _PROC_NAME_LIST |
| assert scan_interval >= 0 |
| self.network_manager = network_manager |
| self.process_name = process_name |
| self.scan_interval = scan_interval |
| self.depservices = depservices |
| self.subservices = subservices |
| self.profile_path = profile_path |
| self.override_blacklisted_devices = override_blacklisted_devices |
| # Auto-detect the network manager process name if unknown. |
| if self.process_name == _UNKNOWN_PROC: |
| self._DetectProcName() |
| if wlans is None: |
| wlans = [] |
| self.wlans = [] |
| self._ConfigureWifi(wlans) |
| |
| logging.info('Created connection manager: wlans=[%s]', |
| ', '.join([x['SSID'] for x in self.wlans])) |
| |
| if start_enabled: |
| if override_blacklisted_devices is None: |
| self.EnableNetworking(reset=False) |
| else: |
| self.EnableNetworking(reset=True) |
| else: |
| self.DisableNetworking(clear=False) |
| |
| def _DetectProcName(self): |
| """Tries to auto-detect the network manager process name.""" |
| # Try to detects the network manager process with pgrep. |
| for process_name in _PROC_NAME_LIST[1:]: |
| if not subprocess.call('pgrep %s' % process_name, |
| shell=True, stdout=self.fnull): |
| self.process_name = process_name |
| return |
| raise ConnectionManagerException("Can't find the network manager process") |
| |
| def _GetInterfaces(self): |
| """Gets the list of all network interfaces.""" |
| device_paths = glob.glob('/sys/class/net/*') |
| interfaces = [os.path.basename(x) for x in device_paths] |
| try: |
| interfaces.remove('lo') |
| except ValueError: |
| logging.info('Local loopback is not found. Skipped') |
| return interfaces |
| |
| def _ConfigureWifi(self, wlans): |
| '''Configures the wireless network settings. |
| |
| The setting will let the network manager auto-connect the preferred |
| wireless networks. |
| |
| Args: |
| wlans: A list of preferred wireless networks and their properties. |
| Each item should be a WLAN object. |
| ''' |
| for wlan in wlans: |
| wlan_dict = { |
| 'Type': 'wifi', |
| 'Mode': 'managed', |
| 'AutoConnect': True, |
| 'SSID': wlan.ssid, |
| 'Security': wlan.security |
| } |
| # "Passphrase" is only needed for secure wifi. |
| if wlan.security is not 'none': |
| wlan_dict.update({ |
| 'Passphrase': wlan.passphrase |
| }) |
| self.wlans.append(wlan_dict) |
| |
| def EnableNetworking(self, reset=True): |
| '''Tells underlying connection manager to try auto-connecting. |
| |
| Args: |
| reset: Force a clean restart of the network services. Remove previous |
| states if there is any. |
| ''' |
| if reset: |
| # Make sure the network services are really stopped. |
| self.DisableNetworking() |
| |
| logging.info('Enabling networking') |
| |
| # Turn on drivers for interfaces. |
| for dev in self._GetInterfaces(): |
| logging.info('ifconfig %s up', dev) |
| subprocess.call('ifconfig %s up' % dev, shell=True, stdout=self.fnull, |
| stderr=self.fnull) |
| |
| # Start network manager. |
| for service in self.depservices + [self.network_manager] + self.subservices: |
| cmd = 'start %s' % service |
| if (service in _MANAGER_LIST and |
| self.override_blacklisted_devices is not None): |
| cmd += ' BLACKLISTED_DEVICES="%s"' % ( |
| ','.join(self.override_blacklisted_devices)) |
| subprocess.call(cmd, shell=True, stdout=self.fnull, stderr=self.fnull) |
| |
| # Configure the network manager to auto-connect wireless networks. |
| try: |
| base_manager = GetBaseNetworkManager() |
| except dbus.exceptions.DBusException: |
| logging.exception('Could not find the network manager service') |
| return False |
| |
| # Configure the wireless network scanning interval. |
| for dev in self._GetInterfaces(): |
| if 'wlan' in dev or 'mlan' in dev: |
| try: |
| device = base_manager.FindElementByNameSubstring('Device', dev) |
| device.SetProperty('ScanInterval', dbus.UInt16(self.scan_interval)) |
| except dbus.exceptions.DBusException: |
| logging.exception('Failed to set scanning interval for interface: %s', |
| dev) |
| except AttributeError: |
| logging.exception('Unable to find the interface: %s', dev) |
| |
| # Set the known wireless networks. |
| for wlan in self.wlans: |
| try: |
| base_manager.manager.ConfigureService(wlan, signature='a{sv}') |
| except dbus.exceptions.DBusException: |
| logging.exception('Unable to configure wireless network: %s', |
| wlan['SSID']) |
| return True |
| |
| def DisableNetworking(self, clear=True): |
| '''Tells underlying connection manager to terminate any existing connection. |
| |
| Args: |
| clear: clear configured profiles related to services. |
| ''' |
| logging.info('Disabling networking') |
| |
| # Stop network manager. |
| for service in self.subservices + [self.network_manager] + self.depservices: |
| subprocess.call('stop %s' % service, shell=True, |
| stdout=self.fnull, stderr=self.fnull) |
| |
| # Turn down drivers for interfaces to really stop the network. |
| for dev in self._GetInterfaces(): |
| subprocess.call('ifconfig %s down' % dev, shell=True, stdout=self.fnull, |
| stderr=self.fnull) |
| |
| # Delete the configured profiles |
| if clear: |
| try: |
| os.remove(self.profile_path % self.process_name) |
| except OSError: |
| logging.exception('Unable to remove the network profile.' |
| ' File non-existent?') |
| |
| def WaitForConnection(self, timeout=_CONNECTION_TIMEOUT_SECS): |
| '''A blocking function that waits until any network is connected. |
| |
| The function will raise an Exception if no network is ready when |
| the time runs out. |
| |
| Args: |
| timeout: Timeout in seconds. |
| ''' |
| t_start = time.clock() |
| while not self.IsConnected(): |
| if time.clock() - t_start > timeout: |
| raise ConnectionManagerException('Not connected') |
| time.sleep(_SLEEP_INTERVAL_SECS) |
| |
| def IsConnected(self): |
| """Returns (network state == online).""" |
| # Check if we are connected to any network. |
| # We can't cache the flimflam object because each time we re-start |
| # the network some filepaths that flimflam works on will change. |
| try: |
| base_manager = GetBaseNetworkManager() |
| except dbus.exceptions.DBusException: |
| # The network manager is not running. |
| return False |
| |
| stat = base_manager.GetSystemState() |
| return stat != 'offline' |
| |
| |
| class DummyConnectionManager(object): |
| '''A dummy connection manager that always reports being connected. |
| |
| Useful, e.g., in the chroot.''' |
| |
| def __init__(self): |
| pass |
| |
| def DisableNetworking(self): |
| logging.warn('DisableNetworking: no network manager is set') |
| |
| def EnableNetworking(self): |
| logging.warn('EnableNetworking: no network manager is set') |
| |
| def WaitForConnection(self, timeout=_CONNECTION_TIMEOUT_SECS): |
| pass |
| |
| def IsConnected(self): |
| return True |
| |
| |
| def PingHost(host, timeout=_PING_TIMEOUT_SECS): |
| '''Checks if we can reach a host. |
| |
| Args: |
| host: The host address. |
| timeout: Timeout in seconds. Integers only. |
| |
| Returns: |
| True if host is successfully pinged. |
| ''' |
| with open(os.devnull, 'w') as fnull: |
| return subprocess.call( |
| 'ping %s -c 1 -w %d' % (host, int(timeout)), |
| shell=True, stdout=fnull, stderr=fnull) |