xref: /aosp_15_r20/external/autotest/client/cros/networking/shill_xmlrpc_server.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1#!/usr/bin/python3
2
3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import dbus
8import logging
9import logging.handlers
10import multiprocessing
11import six
12
13import common
14
15from autotest_lib.client.common_lib import utils
16from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
17from autotest_lib.client.cros import xmlrpc_server
18from autotest_lib.client.cros import constants
19from autotest_lib.client.cros import cros_ui
20from autotest_lib.client.cros import tpm_store
21from autotest_lib.client.cros.networking import shill_proxy
22from autotest_lib.client.cros.networking import wifi_proxy
23from autotest_lib.client.cros.power import sys_power
24
25
26class ShillXmlRpcDelegate(xmlrpc_server.XmlRpcDelegate):
27    """Exposes methods called remotely during WiFi autotests.
28
29    All instance methods of this object without a preceding '_' are exposed via
30    an XMLRPC server.  This is not a stateless handler object, which means that
31    if you store state inside the delegate, that state will remain around for
32    future calls.
33
34    """
35
36    DEFAULT_TEST_PROFILE_NAME = 'test'
37    DBUS_DEVICE = 'Device'
38
39    def __init__(self):
40        self._wifi_proxy = wifi_proxy.WifiProxy()
41        self._tpm_store = tpm_store.TPMStore()
42
43
44    def __enter__(self):
45        super(ShillXmlRpcDelegate, self).__enter__()
46        if not cros_ui.stop(allow_fail=True):
47            logging.error('UI did not stop, there could be trouble ahead.')
48        self._tpm_store.__enter__()
49
50
51    def __exit__(self, exception, value, traceback):
52        super(ShillXmlRpcDelegate, self).__exit__(exception, value, traceback)
53        self._tpm_store.__exit__(exception, value, traceback)
54        self.enable_ui()
55
56
57    @xmlrpc_server.dbus_safe(False)
58    def create_profile(self, profile_name):
59        """Create a shill profile.
60
61        @param profile_name string name of profile to create.
62        @return True on success, False otherwise.
63
64        """
65        self._wifi_proxy.manager.CreateProfile(profile_name)
66        return True
67
68
69    @xmlrpc_server.dbus_safe(False)
70    def push_profile(self, profile_name):
71        """Push a shill profile.
72
73        @param profile_name string name of profile to push.
74        @return True on success, False otherwise.
75
76        """
77        self._wifi_proxy.manager.PushProfile(profile_name)
78        return True
79
80
81    @xmlrpc_server.dbus_safe(False)
82    def pop_profile(self, profile_name):
83        """Pop a shill profile.
84
85        @param profile_name string name of profile to pop.
86        @return True on success, False otherwise.
87
88        """
89        if profile_name is None:
90            self._wifi_proxy.manager.PopAnyProfile()
91        else:
92            self._wifi_proxy.manager.PopProfile(profile_name)
93        return True
94
95
96    @xmlrpc_server.dbus_safe(False)
97    def remove_profile(self, profile_name):
98        """Remove a profile from disk.
99
100        @param profile_name string name of profile to remove.
101        @return True on success, False otherwise.
102
103        """
104        self._wifi_proxy.manager.RemoveProfile(profile_name)
105        return True
106
107
108    @xmlrpc_server.dbus_safe(False)
109    def clean_profiles(self):
110        """Pop and remove shill profiles above the default profile.
111
112        @return True on success, False otherwise.
113
114        """
115        while True:
116            active_profile = self._wifi_proxy.get_active_profile()
117            if six.PY2:
118                profile_props = active_profile.GetProperties(utf8_strings=True)
119            else:
120                profile_props = active_profile.GetProperties()
121            profile_name = self._wifi_proxy.dbus2primitive(
122                    profile_props['Name'])
123            if profile_name == 'default':
124                return True
125            self._wifi_proxy.manager.PopProfile(profile_name)
126            self._wifi_proxy.manager.RemoveProfile(profile_name)
127
128
129    @xmlrpc_server.dbus_safe(False)
130    def configure_service_by_guid(self, raw_params):
131        """Configure a service referenced by a GUID.
132
133        @param raw_params serialized ConfigureServiceParameters.
134
135        """
136        params = xmlrpc_datatypes.deserialize(raw_params)
137        shill = self._wifi_proxy
138        properties = {}
139        if params.autoconnect is not None:
140            properties[shill.SERVICE_PROPERTY_AUTOCONNECT] = params.autoconnect
141        if params.passphrase is not None:
142            properties[shill.SERVICE_PROPERTY_PASSPHRASE] = params.passphrase
143        if properties:
144            self._wifi_proxy.configure_service_by_guid(params.guid, properties)
145        return True
146
147
148    @xmlrpc_server.dbus_safe(False)
149    def configure_wifi_service(self, raw_params):
150        """Configure a WiFi service
151
152        @param raw_params serialized AssociationParameters.
153        @return True on success, False otherwise.
154
155        """
156        params = xmlrpc_datatypes.deserialize(raw_params)
157        return self._wifi_proxy.configure_wifi_service(
158                params.ssid,
159                params.security,
160                params.security_parameters,
161                save_credentials=params.save_credentials,
162                station_type=params.station_type,
163                hidden_network=params.is_hidden,
164                guid=params.guid,
165                autoconnect=params.autoconnect)
166
167
168    def connect_wifi(self, raw_params):
169        """Block and attempt to connect to wifi network.
170
171        @param raw_params serialized AssociationParameters.
172        @return serialized AssociationResult
173
174        """
175        logging.debug('connect_wifi()')
176        params = xmlrpc_datatypes.deserialize(raw_params)
177        params.security_config.install_client_credentials(self._tpm_store)
178        wifi_if = params.bgscan_config.interface
179        if wifi_if is None:
180            logging.info('Using default interface for bgscan configuration')
181            interfaces = self.list_controlled_wifi_interfaces()
182            if not interfaces:
183                return xmlrpc_datatypes.AssociationResult(
184                        failure_reason='No wifi interfaces found?')
185
186            if len(interfaces) > 1:
187                logging.error('Defaulting to first interface of %r', interfaces)
188            wifi_if = interfaces[0]
189        if not self._wifi_proxy.configure_bgscan(
190                wifi_if,
191                method=params.bgscan_config.method,
192                short_interval=params.bgscan_config.short_interval,
193                long_interval=params.bgscan_config.long_interval,
194                signal=params.bgscan_config.signal):
195            return xmlrpc_datatypes.AssociationResult(
196                    failure_reason='Failed to configure bgscan')
197
198        raw = self._wifi_proxy.connect_to_wifi_network(
199                params.ssid,
200                params.security,
201                params.security_parameters,
202                params.save_credentials,
203                station_type=params.station_type,
204                hidden_network=params.is_hidden,
205                guid=params.guid,
206                discovery_timeout_seconds=params.discovery_timeout,
207                association_timeout_seconds=params.association_timeout,
208                configuration_timeout_seconds=params.configuration_timeout)
209        result = xmlrpc_datatypes.AssociationResult.from_dbus_proxy_output(raw)
210        return result
211
212
213    @xmlrpc_server.dbus_safe(False)
214    def delete_entries_for_ssid(self, ssid):
215        """Delete a profile entry.
216
217        @param ssid string of WiFi service for which to delete entries.
218        @return True on success, False otherwise.
219
220        """
221        shill = self._wifi_proxy
222        for profile in shill.get_profiles():
223            if six.PY2:
224                profile_properties = profile.GetProperties(utf8_strings=True)
225            else:
226                profile_properties = profile.GetProperties()
227            profile_properties = shill.dbus2primitive(profile_properties)
228            entry_ids = profile_properties[shill.PROFILE_PROPERTY_ENTRIES]
229            for entry_id in entry_ids:
230                entry = profile.GetEntry(entry_id)
231                if shill.dbus2primitive(entry[shill.ENTRY_FIELD_NAME]) == ssid:
232                    profile.DeleteEntry(entry_id)
233        return True
234
235
236    def init_test_network_state(self):
237        """Create a clean slate for tests with respect to remembered networks.
238
239        For shill, this means popping and removing profiles, removing all WiFi
240        entries from the default profile, and pushing a 'test' profile.
241
242        @return True iff operation succeeded, False otherwise.
243
244        """
245        self.clean_profiles()
246        self._wifi_proxy.remove_all_wifi_entries()
247        self.remove_profile(self.DEFAULT_TEST_PROFILE_NAME)
248        worked = self.create_profile(self.DEFAULT_TEST_PROFILE_NAME)
249        if worked:
250            worked = self.push_profile(self.DEFAULT_TEST_PROFILE_NAME)
251        return worked
252
253
254    @xmlrpc_server.dbus_safe(None)
255    def list_controlled_wifi_interfaces(self):
256        """List WiFi interfaces controlled by shill.
257
258        @return list of string WiFi device names (e.g. ['mlan0'])
259
260        """
261        ret = []
262        devices = self._wifi_proxy.get_devices()
263        for device in devices:
264            if six.PY2:
265                properties = device.GetProperties(utf8_strings=True)
266            else:
267                properties = device.GetProperties()
268            properties = self._wifi_proxy.dbus2primitive(properties)
269            if properties[self._wifi_proxy.DEVICE_PROPERTY_TYPE] != 'wifi':
270                continue
271            ret.append(properties[self._wifi_proxy.DEVICE_PROPERTY_NAME])
272        return ret
273
274
275    @xmlrpc_server.dbus_safe(False)
276    def disconnect(self, ssid):
277        """Attempt to disconnect from the given ssid.
278
279        Blocks until disconnected or operation has timed out.  Returns True iff
280        disconnect was successful.
281
282        @param ssid string network to disconnect from.
283        @return bool True on success, False otherwise.
284
285        """
286        logging.debug('disconnect()')
287        result = self._wifi_proxy.disconnect_from_wifi_network(ssid)
288        successful, duration, message = result
289        if successful:
290            level = logging.info
291        else:
292            level = logging.error
293        level('Disconnect result: %r, duration: %d, reason: %s',
294              successful, duration, message)
295        return successful is True
296
297
298    def wait_for_service_states(self, ssid, states, timeout_seconds):
299        """Wait for service to achieve one state out of a list of states.
300
301        @param ssid string the network to connect to (e.g. 'GoogleGuest').
302        @param states tuple the states for which to wait
303        @param timeout_seconds int seconds to wait for a state
304
305        """
306        return self._wifi_proxy.wait_for_service_states(
307                ssid, states, timeout_seconds)
308
309
310    @xmlrpc_server.dbus_safe(None)
311    def get_service_order(self):
312        """Get the shill service order.
313
314        @return string service order on success, None otherwise.
315
316        """
317        return str(self._wifi_proxy.manager.GetServiceOrder())
318
319
320    @xmlrpc_server.dbus_safe(False)
321    def set_service_order(self, order):
322        """Set the shill service order.
323
324        @param order string comma-delimited service order (eg. 'ethernet,wifi')
325        @return bool True on success, False otherwise.
326
327        """
328        self._wifi_proxy.manager.SetServiceOrder(dbus.String(order))
329        return True
330
331
332    @xmlrpc_server.dbus_safe(None)
333    def get_service_properties(self, ssid):
334        """Get a dict of properties for a service.
335
336        @param ssid string service to get properties for.
337        @return dict of Python friendly built-in types or None on failures.
338
339        """
340        discovery_params = {self._wifi_proxy.SERVICE_PROPERTY_TYPE: 'wifi',
341                            self._wifi_proxy.SERVICE_PROPERTY_NAME: ssid}
342        service_path = self._wifi_proxy.manager.FindMatchingService(
343                discovery_params)
344        service_object = self._wifi_proxy.get_dbus_object(
345                self._wifi_proxy.DBUS_TYPE_SERVICE, service_path)
346        if six.PY2:
347            service_properties = service_object.GetProperties(
348                    utf8_strings=True)
349        else:
350            service_properties = service_object.GetProperties()
351        return self._wifi_proxy.dbus2primitive(service_properties)
352
353
354    @xmlrpc_server.dbus_safe(None)
355    def get_manager_properties(self):
356        if six.PY2:
357            manager_props = self._wifi_proxy.manager.GetProperties(
358                    utf8_strings=True)
359        else:
360            manager_props = self._wifi_proxy.manager.GetProperties()
361        return self._wifi_proxy.dbus2primitive(manager_props)
362
363
364    @xmlrpc_server.dbus_safe(None)
365    def get_manager_property(self, property_name):
366        prop_value = self._wifi_proxy.get_dbus_property(
367                self._wifi_proxy.manager,  property_name)
368        return self._wifi_proxy.dbus2primitive(prop_value)
369
370
371    @xmlrpc_server.dbus_safe(False)
372    def set_manager_property(self, property_name, property_value):
373        self._wifi_proxy.set_dbus_property(self._wifi_proxy.manager,
374                                           property_name, property_value)
375        return True
376
377    @xmlrpc_server.dbus_safe(False)
378    def set_optional_manager_property(self, property_name, property_value):
379        """Set optional manager property.
380
381        @param property_name String name of property to set
382        @param property_value String value to set property to
383        @return True on success, False otherwise.
384
385        """
386        self._wifi_proxy.set_optional_dbus_property(
387                self._wifi_proxy.manager, property_name, property_value)
388        return True
389
390    @xmlrpc_server.dbus_safe(False)
391    def get_active_wifi_SSIDs(self):
392        """@return list of string SSIDs with at least one BSS we've scanned."""
393        return self._wifi_proxy.get_active_wifi_SSIDs()
394
395
396    @xmlrpc_server.dbus_safe(False)
397    def set_sched_scan(self, enable):
398        """Configure scheduled scan.
399
400        @param enable bool flag indicating to enable/disable scheduled scan.
401        @return True on success, False otherwise.
402
403        """
404        self._wifi_proxy.manager.set_sched_scan(enable)
405        return True
406
407
408    def enable_ui(self):
409        """@return True iff the UI was successfully started."""
410        return cros_ui.start(allow_fail=True, wait_for_login_prompt=False) == 0
411
412
413    def sync_time_to(self, epoch_seconds):
414        """Sync time on the DUT to |epoch_seconds| from the epoch.
415
416        @param epoch_seconds: float number of seconds from the epoch.
417
418        """
419        utils.run('date -u --set=@%f' % epoch_seconds)
420        return True
421
422
423    @staticmethod
424    def do_suspend(seconds):
425        """Suspend DUT using the power manager.
426
427        @param seconds: The number of seconds to suspend the device.
428
429        """
430        return sys_power.do_suspend(seconds)
431
432
433    @staticmethod
434    def do_suspend_bg(seconds):
435        """Suspend DUT using the power manager - non-blocking.
436
437        @param seconds int The number of seconds to suspend the device.
438
439        """
440        process = multiprocessing.Process(target=sys_power.do_suspend,
441                                          args=(seconds, 1))
442        process.start()
443        return True
444
445
446    @xmlrpc_server.dbus_safe(None)
447    def get_dbus_property_on_device(self, wifi_interface, prop_name):
448        """Get a property for the given WiFi device.
449
450        @param wifi_interface: string name of interface being queried.
451        @param prop_name: the name of the property.
452        @return the current value of the property.
453
454        """
455        dbus_object = self._wifi_proxy.find_object(
456                self.DBUS_DEVICE, {'Name': wifi_interface})
457        if dbus_object is None:
458            return None
459
460        object_properties = dbus_object.GetProperties(utf8_strings=True)
461        if prop_name not in object_properties:
462            return None
463
464        return self._wifi_proxy.dbus2primitive(
465                object_properties[prop_name])
466
467
468    @xmlrpc_server.dbus_safe(False)
469    def set_dbus_property_on_device(self, wifi_interface, prop_name, value):
470        """Set a property on the given WiFi device.
471
472        @param wifi_interface: the device to set a property for.
473        @param prop_name: the name of the property.
474        @param value: the desired value of the property.
475        @return True if successful, False otherwise.
476
477        """
478        device_object = self._wifi_proxy.find_object(
479                self.DBUS_DEVICE, {'Name': wifi_interface})
480        if device_object is None:
481            return False
482
483        shill_proxy.ShillProxy.set_dbus_property(device_object,
484                                                 prop_name,
485                                                 value)
486        return True
487
488
489    @xmlrpc_server.dbus_safe(False)
490    def request_roam_dbus(self, bssid, interface):
491        """Request that we roam to the specified BSSID.
492
493        Note that this operation assumes that:
494
495        1) We're connected to an SSID for which |bssid| is a member.
496        2) There is a BSS with an appropriate ID in our scan results.
497
498        @param bssid: string BSSID of BSS to roam to.
499        @param interface: string name of interface to request roam for.
500
501        """
502
503        device_object = self._wifi_proxy.find_object(
504                self.DBUS_DEVICE, {'Name': interface})
505        if device_object is None:
506            return False
507        device_object.RequestRoam(bssid)
508        return True
509
510
511    @xmlrpc_server.dbus_safe(False)
512    def set_device_enabled(self, wifi_interface, enabled):
513        """Enable or disable the WiFi device.
514
515        @param wifi_interface: string name of interface being modified.
516        @param enabled: boolean; true if this device should be enabled,
517                false if this device should be disabled.
518        @return True if it worked; false, otherwise
519
520        """
521        interface = {'Name': wifi_interface}
522        dbus_object = self._wifi_proxy.find_object(self.DBUS_DEVICE,
523                                                   interface)
524        if dbus_object is None:
525            return False
526
527        if enabled:
528            dbus_object.Enable()
529        else:
530            dbus_object.Disable()
531        return True
532
533    @xmlrpc_server.dbus_safe(False)
534    def add_wake_packet_source(self, wifi_interface, source_ip):
535        """Set up the NIC to wake on packets from the given source IP.
536
537        @param wifi_interface: string name of interface to establish WoWLAN on.
538        @param source_ip: string IP address of packet source, i.e. "127.0.0.1"
539
540        @return True on success, False otherwise.
541
542        """
543        device_object = self._wifi_proxy.find_object(
544                self.DBUS_DEVICE, {'Name': wifi_interface})
545        if device_object is None:
546            return False
547        device_object.AddWakeOnPacketConnection(source_ip)
548        return True
549
550
551    @xmlrpc_server.dbus_safe(False)
552    def remove_wake_packet_source(self, wifi_interface, source_ip):
553        """Stop waking on packets from the given source IP.
554
555        @param wifi_interface: string name of interface to establish WoWLAN on.
556        @param source_ip: string IP address of packet source, i.e. "127.0.0.1"
557
558        @return True on success, False otherwise.
559
560        """
561        device_object = self._wifi_proxy.find_object(
562                self.DBUS_DEVICE, {'Name': wifi_interface})
563        if device_object is None:
564            return False
565        device_object.RemoveWakeOnPacketConnection(source_ip)
566        return True
567
568
569    @xmlrpc_server.dbus_safe(False)
570    def remove_all_wake_packet_sources(self, wifi_interface):
571        """Stop waking on packets from any IP.
572
573        @param wifi_interface: string name of interface to establish WoWLAN on.
574
575        @return True on success, False otherwise.
576
577        """
578        device_object = self._wifi_proxy.find_object(
579                self.DBUS_DEVICE, {'Name': wifi_interface})
580        if device_object is None:
581            return False
582        device_object.RemoveAllWakeOnPacketConnections()
583        return True
584
585
586    @xmlrpc_server.dbus_safe(False)
587    def request_scan(self):
588        """Request a scan from shill.
589
590        @return True on success, False otherwise.
591
592        """
593        self._wifi_proxy.manager.RequestScan('wifi')
594        return True
595
596
597
598if __name__ == '__main__':
599    logging.basicConfig(level=logging.DEBUG)
600    handler = logging.handlers.SysLogHandler(address = '/dev/log')
601    formatter = logging.Formatter(
602            'shill_xmlrpc_server: [%(levelname)s] %(message)s')
603    handler.setFormatter(formatter)
604    logging.getLogger().addHandler(handler)
605    logging.debug('shill_xmlrpc_server main...')
606    server = xmlrpc_server.XmlRpcServer('localhost',
607                                         constants.SHILL_XMLRPC_SERVER_PORT)
608    server.register_delegate(ShillXmlRpcDelegate())
609    server.run()
610