blob: 2e82c7d27d2d36377728a54b3b6d962c9db46fc0 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2011 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.
"""Connect to a WiFi service and report the amount of time it took
This script initiates a connection to a WiFi service and reports
the time to major state changes (assoc, config). If the connection
fails within the desired time, it outputs the contents of the log
files during that intervening time.
"""
import dbus
import gobject
import logging
import optparse
import sys
import time
import traceback
# Once these are no longer copied to DUTs manually, this should become
# from autotest_lib.client.common_lib.cros.site_wlan import constants
import constants
from site_wlan_wait_state import *
CONNECTION_MANAGER_ERROR = constants.CONNECTION_MANAGER + '.Error'
ERROR_INPROGRESS = CONNECTION_MANAGER_ERROR + '.InProgress'
ERROR_UNKNOWNMETHOD = CONNECTION_MANAGER_ERROR + '.UnknownMethod'
ERROR_ALREADYCONNECTED = CONNECTION_MANAGER_ERROR + '.AlreadyConnected'
connect_quirks = {}
def convert_dbus_value(value):
if value.__class__ == dbus.Byte:
return int(value)
elif value.__class__ == dbus.Boolean:
return bool(value)
else:
return value
class ConnectStateHandler(StateHandler):
def __init__(self, dbus_bus, connection_settings, hidden, timeout,
start_time=None, debug=False, scan_retry=8):
self.connection_settings = connection_settings
self.acquisition_time = None
self.authentication_time = None
self.configuration_time = None
self.frequency = 0
self.hidden = hidden
self.phymode = None
self.security = None
self.service_handle = None
self.scan_timeout = None
self.scan_retry = scan_retry
StateHandler.__init__(self, dbus_bus,
[(connection_settings['SSID'], 'State', 'ready',
False, True)],
timeout, None, timeout, debug)
if start_time:
self.run_start_time = start_time
self.bus.add_signal_receiver(
self.SupplicantChangeCallback,
signal_name='PropertiesChanged',
dbus_interface=constants.SUPPLICANT_INTERFACE)
def _GetMatchedService(self, service_list):
"""Get a service matching the connection setting from a list of services.
Args:
service_list: An array of (1) DBus objects for Services with a specified
ssid.
Returns:
The DBus object for the service that matches our connection_settings
(None if nothing matched).
"""
matched_service = None
for svc in service_list:
props = svc.GetProperties()
set_props = {}
for key, val in self.connection_settings.items():
prop_val = convert_dbus_value(props.get(key))
if key != 'SSID' and prop_val != val:
if key == "EAP.UseSystemCAs":
set_props[key] = bool(val)
elif (key in ['Passphrase', 'SaveCredentials'] or
key.startswith('EAP.')):
set_props[key] = val
else:
self.Debug(
'Service key mismatch: %s (desired "%s" != available "%s")' %
(key, val, str(prop_val)))
break
else:
for key, val in set_props.iteritems():
try:
self.Debug('Setting property %s to %s' % (key, val))
svc.SetProperty(key, val)
except dbus.exceptions.DBusException, e:
self.failure = ('SetProperty: DBus exception %s for set of %s' %
(e, key))
raise e
matched_service = svc
if self.scan_timeout is not None:
gobject.source_remove(self.scan_timeout)
self.scan_timeout = None
return matched_service
def FindService(self, path_list=None):
service = None
try:
service = self._GetMatchedService(
FindObjects('Service', 'SSID', self.service_name,
path_list=path_list))
except dbus.exceptions.DBusException, e:
# Failure reason must have been set by _GetMatchedService().
return None
if not service and self.hidden:
try:
path = manager.GetService(
dbus.Dictionary(self.connection_settings, signature='sv'))
service = dbus.Interface(
self.bus.get_object(constants.CONNECTION_MANAGER, path),
constants.CONNECTION_MANAGER_SERVICE)
except dbus.exceptions.DBusException, e:
self.failure = ('GetService: DBus exception %s for settings %s' %
(e, self.connection_settings))
return None
elif not service and not self.scan_timeout:
self.DoScan()
return None
elif not service:
return None
if not self.acquisition_time:
self.acquisition_time = time.time()
# If service isn't already connecting or connected, start now
if (service.GetProperties()['State'] not in
('association', 'configuration', 'ready')):
try:
service.Connect()
except dbus.exceptions.DBusException, e:
if e.get_dbus_name() == ERROR_INPROGRESS:
self.Debug('Service was already in progress (state=%s)' %
service.GetProperties().get('State'))
connect_quirks['in_progress'] = 1
else:
print 'FAIL(acquire): DBus exception in Connect() %s' % e
ErrExit(2)
else:
self.Debug("skipping Connect call, service is in %s state" %
service.GetProperties()['State'])
self.service_handle = service
return service
def DoScan(self):
self.scan_timeout = None
self.Debug('Service not found; requesting scan...')
try:
manager.RequestScan('wifi')
except dbus.exceptions.DBusException, e:
if e.get_dbus_name() != ERROR_INPROGRESS:
raise
self.scan_timeout = gobject.timeout_add(int(self.scan_retry*1000),
self.DoScan)
def StateChanged(self):
# If we entered the "configuration" state, mark that down
if self.svc_state == 'configuration':
self.configuration_time = time.time()
# NB: do this on all changes in case configuration is skipped
props = self.service_handle.GetProperties()
self.security = props.get('Security', None)
self.frequency = props.get('WiFi.Frequency', 0)
self.phymode = props.get('WiFi.PhyMode', None)
def Stage(self):
if not self.wait_path:
return 'acquire'
elif not self.configuration_time:
return 'assoc'
else:
return 'config'
def SupplicantChangeCallback(self, args, **kwargs):
if 'State' in args:
state = args['State']
self.Debug('Supplicant state is \'%s\'' % state)
if (not self.authentication_time and
(state == 'authenticating' or state == 'associating')):
self.authentication_time = time.time()
elif state == 'inactive' or state == 'disconnected':
self.authentication_time = None
def ErrExit(code):
try:
handler.service_handle.Disconnect()
except:
pass
DumpLogs(logs)
sys.exit(code)
def split_psk(settings, psk):
cert_args = psk.split(':')
for i in xrange(0, len(cert_args), 2):
settings[cert_args[i]] = cert_args[i+1]
def main(argv):
parser = optparse.OptionParser('Usage: %prog [options...] '
'ssid security psk assoc_timeout cfg_timeout')
parser.add_option('--hidden', dest='hidden', action='store_true',
help='This is a hidden network')
parser.add_option('--debug', dest='debug', action='store_true',
help='Report state changes and other debug info')
parser.add_option('--mode', dest='mode', default='managed',
help='AP mode')
parser.add_option('--nosave', dest='save_creds', action='store_false',
default=True, help='Do not save credentials')
(options, args) = parser.parse_args(argv[1:])
if len(argv) <= 4:
parser.error('Required arguments: ssid security psk assoc_timeout '
'config_timeout')
ssid = args[0]
security = args[1]
psk = args[2]
assoc_timeout = float(args[3])
config_timeout = float(args[4])
connection_settings = {
'Type': 'wifi',
'Mode': options.mode,
'SSID': ssid,
'SaveCredentials' : options.save_creds
}
if security:
connection_settings['Security'] = security
if security == '802_1x':
split_psk(connection_settings, psk)
elif security == '802_1x_wep':
split_psk(connection_settings, psk)
connection_settings['Security'] = 'wep'
elif security in ['wep', 'wpa', 'rsn', 'psk']:
connection_settings['Passphrase'] = psk
global logs
global handler
logs = OpenLogs('/var/log/messages', '/var/log/net.log')
assoc_start = time.time()
handler = ConnectStateHandler(bus, connection_settings, options.hidden,
assoc_timeout, assoc_start, options.debug)
try:
if handler.NextState():
handler.RunLoop()
except dbus.exceptions.DBusException, e:
if e.get_dbus_name() == ERROR_INPROGRESS:
connect_quirks['in_progress'] = 1
print>>sys.stderr, 'Previous connect is still in progress!'
if e.get_dbus_name() == ERROR_UNKNOWNMETHOD:
connect_quirks['lost_dbus_connect'] = 1
print>>sys.stderr, 'Lost the service handle during Connect()!'
if e.get_dbus_name() != ERROR_ALREADYCONNECTED:
print 'FAIL(%s): ssid %s DBus exception %s' % (handler.Stage(), ssid, e)
ErrExit(2)
except Exception, e:
print 'FAIL(%s): ssid %s exception %s' % (handler.Stage(), ssid, e)
traceback.print_exc(file=sys.stderr)
ErrExit(2)
if handler.Failure():
print 'FAIL(%s): ssid %s %s' % (handler.Stage(), ssid, handler.Failure())
ErrExit(2)
end = time.time()
config_start = handler.configuration_time
acq_start = handler.acquisition_time
if acq_start:
acquire_time = acq_start - assoc_start
assoc_start = acq_start
else:
acquire_time = 0.0
auth_start = handler.authentication_time
if auth_start:
wpa_select_time = auth_start - assoc_start
assoc_start = auth_start
# auth_start and assoc_start are timestamps coming from two different
# processes, and when they are close, auth_start can occur before
# assoc_start, resulting in a negative wpa_select_time. In this case, set
# wpa_select_time to zero.
if wpa_select_time < 0:
wpa_select_time = 0.0
else:
wpa_select_time = 0.0
if config_start:
config_time = end - config_start
if config_time < 0.0:
config_time = 0.0
assoc_time = config_start - assoc_start
if assoc_time < 0.0:
assoc_time = 0.0
else:
config_time = 0.0
assoc_time = end - assoc_start
if not handler.Success():
print ('TIMEOUT(%s): ssid %s acquire %3.3f wpa_select %3.3f assoc %3.3f '
'config %3.3f secs state %s' %
(handler.Stage(), ssid, acquire_time, wpa_select_time, assoc_time,
config_time,
handler.svc_state))
ErrExit(3)
print ('OK %3.3f %3.3f %3.3f %3.3f %d %s %s %s '
'(acquire wpa_select assoc and config times in sec, quirks)' %
(acquire_time, wpa_select_time, assoc_time, config_time,
handler.frequency, handler.phymode, handler.security,
str(connect_quirks.keys())))
if connect_quirks:
DumpLogs(logs)
sys.exit(0)
if __name__ == '__main__':
main(sys.argv)