All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/5] test-runner: Reserve radios for wpa_supplicant
@ 2020-10-09 18:08 Andrew Zaborowski
  2020-10-09 18:08 ` [PATCH 2/5] autotests: Basic P2P python API Andrew Zaborowski
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Andrew Zaborowski @ 2020-10-09 18:08 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 3076 bytes --]

Add support for a WPA_SUPPLICANT section in  hw.conf where
'radN=<config_path>' lines will only reserve radios and create
interfaces for the autotest to be able to start wpa_supplicant on them,
i.e. this prevents iwd or hostapd from being started on them but doesn't
start a wpa_supplicant instance by itself.
---
 tools/test-runner | 24 +++++++++++++++++-------
 1 file changed, 17 insertions(+), 7 deletions(-)

diff --git a/tools/test-runner b/tools/test-runner
index 3b9c1854..dfad98f6 100755
--- a/tools/test-runner
+++ b/tools/test-runner
@@ -248,10 +248,11 @@ class Process:
 				raise Exception("Timed out waiting for socket")
 
 class Interface:
-	def __init__(self, name, config):
+	def __init__(self, name, config, radio):
 		self.name = name
 		self.ctrl_interface = '/var/run/hostapd/' + name
 		self.config = config
+		self.radio = radio
 
 	def __del__(self):
 		Process(['iw', 'dev', self.name, 'del'], True)
@@ -270,17 +271,15 @@ class Radio:
 		print("Removing radio %s" % self.name)
 		self.interface = None
 
-	def create_interface(self, hapd):
+	def create_interface(self, config, use):
 		global intf_id
 
 		ifname = 'wln%s' % intf_id
 
 		intf_id += 1
 
-		self.interface = Interface(ifname, hapd.config)
-		# IWD does not use interfaces in test-runner so any created
-		# interface is assumed to be used by hostapd.
-		self.use = 'hostapd'
+		self.interface = Interface(ifname, config, self)
+		self.use = use
 
 		Process(['iw', 'phy', self.name, 'interface', 'add', ifname,
 				'type', 'managed'], True)
@@ -358,7 +357,7 @@ class HostapdInstance:
 		self.radio = radio
 		self.config = config
 
-		self.intf = radio.create_interface(self)
+		self.intf = radio.create_interface(self.config, 'hostapd')
 		self.intf.set_interface_state('up')
 
 	def __del__(self):
@@ -473,6 +472,7 @@ class TestContext:
 		self.args = args
 		self.hw_config = None
 		self.hostapd = None
+		self.wpas_interfaces = None
 		self.cur_radio_id = 0
 		self.cur_iface_id = 0
 		self.radios = []
@@ -623,6 +623,14 @@ class TestContext:
 
 		self.hostapd = Hostapd(self, hapd_radios, hapd_configs, radius_config)
 
+	def start_wpas_interfaces(self):
+		if 'WPA_SUPPLICANT' not in self.hw_config:
+			return
+
+		settings = self.hw_config['WPA_SUPPLICANT']
+		wpas_radios = [rad for rad in self.radios if rad.name in settings]
+		self.wpas_interfaces = [rad.create_interface(settings[rad.name], 'wpas') for rad in wpas_radios]
+
 	def start_ofono(self):
 		sim_keys = self.hw_config['SETUP'].get('sim_keys', None)
 		if not sim_keys:
@@ -690,6 +698,7 @@ class TestContext:
 	def stop_test_processes(self):
 		self.radios = []
 		self.hostapd = None
+		self.wpas_interfaces = None
 		self.iwd_extra_options = None
 
 		for p in [p for p in self.processes if p.multi_test is False]:
@@ -884,6 +893,7 @@ def pre_test(ctx, test):
 	ctx.start_dbus_monitor()
 	ctx.start_radios()
 	ctx.start_hostapd()
+	ctx.start_wpas_interfaces()
 	ctx.start_ofono()
 
 	if ctx.args.log:
-- 
2.25.1

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/5] autotests: Basic P2P python API
  2020-10-09 18:08 [PATCH 1/5] test-runner: Reserve radios for wpa_supplicant Andrew Zaborowski
@ 2020-10-09 18:08 ` Andrew Zaborowski
  2020-10-09 18:08 ` [PATCH 3/5] autotests: Add basic wpa_supplicant P2P python wrapper Andrew Zaborowski
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Andrew Zaborowski @ 2020-10-09 18:08 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 6512 bytes --]

Add a basic wrapper for the P2P-related DBus interfaces similar to the
existing classes in iwd.py.  WFD is not included.
---
 autotests/util/iwd.py | 121 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 118 insertions(+), 3 deletions(-)

diff --git a/autotests/util/iwd.py b/autotests/util/iwd.py
index ef843a95..7e3676d9 100755
--- a/autotests/util/iwd.py
+++ b/autotests/util/iwd.py
@@ -35,6 +35,10 @@ IWD_SIGNAL_AGENT_INTERFACE =    'net.connman.iwd.SignalLevelAgent'
 IWD_AP_INTERFACE =              'net.connman.iwd.AccessPoint'
 IWD_ADHOC_INTERFACE =           'net.connman.iwd.AdHoc'
 IWD_STATION_INTERFACE =         'net.connman.iwd.Station'
+IWD_P2P_INTERFACE =             'net.connman.iwd.p2p.Device'
+IWD_P2P_PEER_INTERFACE =        'net.connman.iwd.p2p.Peer'
+IWD_P2P_SERVICE_MANAGER_INTERFACE = 'net.connman.iwd.p2p.ServiceManager'
+IWD_P2P_WFD_INTERFACE =         'net.connman.iwd.p2p.Display'
 
 IWD_AGENT_MANAGER_PATH =        '/net/connman/iwd'
 IWD_TOP_LEVEL_PATH =            '/'
@@ -767,9 +771,107 @@ class PSKAgent(dbus.service.Object):
         return passwd
 
 
+class P2PDevice(IWDDBusAbstract):
+    _iface_name = IWD_P2P_INTERFACE
+
+    def __init__(self, *args, **kwargs):
+        self._discovery_request = False
+        self._peer_dict = {}
+        IWDDBusAbstract.__init__(self, *args, **kwargs)
+
+    @property
+    def name(self):
+        return str(self._properties['Name'])
+
+    @name.setter
+    def name(self, name):
+        self._prop_proxy.Set(self._iface_name, 'Name', name)
+
+    @property
+    def enabled(self):
+        return bool(self._properties['Enabled'])
+
+    @enabled.setter
+    def enabled(self, enabled):
+        self._prop_proxy.Set(self._iface_name, 'Enabled', enabled)
+
+    @property
+    def discovery_request(self):
+        return self._discovery_request
+
+    @discovery_request.setter
+    def discovery_request(self, req):
+        if self._discovery_request == bool(req):
+            return
+
+        if bool(req):
+            self._iface.RequestDiscovery()
+        else:
+            self._iface.ReleaseDiscovery()
+
+        self._discovery_request = bool(req)
+
+    def get_peers(self):
+        old_dict = self._peer_dict
+        self._peer_dict = {}
+
+        for path, rssi in self._iface.GetPeers():
+            self._peer_dict[path] = old_dict[path] if path in old_dict else P2PPeer(path)
+            self._peer_dict[path].rssi = rssi
+
+        return self._peer_dict
+
+
+class P2PPeer(IWDDBusAbstract):
+    _iface_name = IWD_P2P_PEER_INTERFACE
+
+    @property
+    def name(self):
+        return str(self._properties['Name'])
+
+    @property
+    def category(self):
+        return str(self._properties['DeviceCategory'])
+
+    @property
+    def subcategory(self):
+        return str(self._properties['DeviceSubcategory'])
+
+    @property
+    def connected(self):
+        return bool(self._properties['Connected'])
+
+    @property
+    def connected_interface(self):
+        return str(self._properties['ConnectedInterface'])
+
+    @property
+    def connected_ip(self):
+        return str(self._properties['ConnectedIP'])
+
+    def connect(self, wait=True, pin=None):
+        if pin is None:
+            self._iface.PushButton(dbus_interface=IWD_WSC_INTERFACE,
+                            reply_handler=self._success,
+                            error_handler=self._failure)
+        else:
+            self._iface.StartPin(pin,
+                            dbus_interface=IWD_WSC_INTERFACE,
+                            reply_handler=self._success,
+                            error_handler=self._failure)
+
+        if wait:
+            self._wait_for_async_op()
+            return (self.connected_interface, self.connected_ip)
+
+    def disconnect(self):
+        self._iface.Disconnect()
+
+
 class DeviceList(collections.Mapping):
     def __init__(self, iwd):
         self._dict = {}
+        self._p2p_dict = {}
 
         iwd._object_manager.connect_to_signal("InterfacesAdded",
                                                self._interfaces_added_handler)
@@ -782,6 +884,8 @@ class DeviceList(collections.Mapping):
             for interface in objects[path]:
                 if interface == IWD_DEVICE_INTERFACE:
                     self._dict[path] = Device(path, objects[path][interface])
+                elif interface == IWD_P2P_INTERFACE:
+                    self._p2p_dict[path] = P2PDevice(path, objects[path][interface])
 
     def __getitem__(self, key):
         return self._dict.__getitem__(key)
@@ -798,10 +902,18 @@ class DeviceList(collections.Mapping):
     def _interfaces_added_handler(self, path, interfaces):
         if IWD_DEVICE_INTERFACE in interfaces:
             self._dict[path] = Device(path, interfaces[IWD_DEVICE_INTERFACE])
+        elif IWD_P2P_INTERFACE in interfaces:
+            self._p2p_dict[path] = P2PDevice(path, interfaces[IWD_P2P_INTERFACE])
 
     def _interfaces_removed_handler(self, path, interfaces):
         if IWD_DEVICE_INTERFACE in interfaces:
             del self._dict[path]
+        elif IWD_P2P_INTERFACE in interfaces:
+            del self._p2p_dict[path]
+
+    @property
+    def p2p_dict(self):
+        return self._p2p_dict
 
 
 class IWD(AsyncOpAbstract):
@@ -943,9 +1055,9 @@ class IWD(AsyncOpAbstract):
     def remove_from_storage(file_name):
         os.system('rm -rf ' + IWD_STORAGE_DIR + '/\'' + file_name + '\'')
 
-    def list_devices(self, wait_to_appear = 0, max_wait = 50):
+    def list_devices(self, wait_to_appear = 0, max_wait = 50, p2p = False):
         if not wait_to_appear:
-            return list(self._devices.values())
+            return list(self._devices.values() if not p2p else self._devices.p2p_dict.values())
 
         self._wait_timed_out = False
         def wait_timeout_cb():
@@ -963,7 +1075,10 @@ class IWD(AsyncOpAbstract):
             if not self._wait_timed_out:
                 GLib.source_remove(timeout)
 
-        return list(self._devices.values())
+        return list(self._devices.values() if not p2p else self._devices.p2p_dict.values())
+
+    def list_p2p_devices(self, *args, **kwargs):
+        return self.list_devices(*args, **kwargs, p2p=True)
 
     def list_known_networks(self):
         '''Returns the list of KnownNetwork objects.'''
-- 
2.25.1

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 3/5] autotests: Add basic wpa_supplicant P2P python wrapper
  2020-10-09 18:08 [PATCH 1/5] test-runner: Reserve radios for wpa_supplicant Andrew Zaborowski
  2020-10-09 18:08 ` [PATCH 2/5] autotests: Basic P2P python API Andrew Zaborowski
@ 2020-10-09 18:08 ` Andrew Zaborowski
  2020-10-09 18:08 ` [PATCH 4/5] autotests: Move some variables from IWD class to instance Andrew Zaborowski
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Andrew Zaborowski @ 2020-10-09 18:08 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 9727 bytes --]

Add the new wpas.Wpas class roughly based on hostapd.HostapdCLI but only
adding methods for the P2P-related stuff.

Adding "wpa_supplicant" to -v will enable output from the wpa_supplicant
process to be printed and "wpa_supplicant-dbg" will make it more verbose
("wpa_supplicant" is not needed because it seems to be automatically
enabled because of the glob matching in ctx.is_verbose)
---
 autotests/util/wpas.py | 242 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 242 insertions(+)
 create mode 100644 autotests/util/wpas.py

diff --git a/autotests/util/wpas.py b/autotests/util/wpas.py
new file mode 100644
index 00000000..ec19b101
--- /dev/null
+++ b/autotests/util/wpas.py
@@ -0,0 +1,242 @@
+#!/usr/bin/python3
+import os
+import socket
+from gi.repository import GLib
+from config import ctx
+
+ctrl_count = 0
+
+class Wpas:
+    def _start_wpas(self, config_name=None, p2p=False):
+        global ctrl_count
+
+        main_interface = None
+        for interface in ctx.wpas_interfaces:
+            if config_name is None or interface.config == config_name:
+                if main_interface is not None:
+                    raise Exception('More than was wpa_supplicant interface matches given config')
+                main_interface = interface
+
+        if main_interface is None:
+            raise Exception('No matching wpa_supplicant interface')
+
+        ifname = main_interface.name
+        if p2p:
+            ifname = 'p2p-dev-' + ifname
+
+        self.interface = main_interface
+        self.ifname = ifname
+        self.config_path = '/tmp/' + self.interface.config
+        self.config = self._get_config()
+        self.socket_path = self.config['ctrl_interface']
+        self.io_watch = None
+
+        cmd = ['wpa_supplicant', '-i', self.interface.name, '-c', self.config_path]
+        if ctx.is_verbose('wpa_supplicant-dbg'):
+            cmd += ['-d']
+
+        self.wpa_supplicant = ctx.start_process(cmd)
+
+        self.local_ctrl = '/tmp/wpas_' + str(os.getpid()) + '_' + str(ctrl_count)
+        ctrl_count = ctrl_count + 1
+        self.ctrl_sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+        self.ctrl_sock.bind(self.local_ctrl)
+
+        self.remote_ctrl = self.socket_path + '/' + self.ifname
+        self.wpa_supplicant.wait_for_socket(self.remote_ctrl, 2)
+        self.ctrl_sock.connect(self.remote_ctrl)
+        self.io_watch = GLib.io_add_watch(self.ctrl_sock, GLib.IO_IN, self._handle_data_in)
+
+        self.p2p_peers = {}
+        self.p2p_go_neg_requests = {}
+        self.p2p_clients = {}
+        self.p2p_group = None
+
+        self._rx_data = []
+        self._ctrl_request('ATTACH')
+        self.wait_for_event('OK')
+
+    def __init__(self, *args, **kwargs):
+        self._start_wpas(*args, **kwargs)
+
+    def _get_config(self):
+        f = open(self.config_path)
+        lines = f.readlines()
+        f.close()
+        return dict([[v.strip() for v in kv] for kv in [l.split('#', 1)[0].split('=', 1) for l in lines] if len(kv) == 2])
+
+    def wait_for_event(self, event, timeout=10):
+        self._wait_timed_out = False
+
+        def wait_timeout_cb():
+            self._wait_timed_out = True
+            return False
+
+        timeout = GLib.timeout_add_seconds(timeout, wait_timeout_cb)
+        context = ctx.mainloop.get_context()
+
+        while True:
+            context.iteration(may_block=True)
+
+            if event in self._rx_data:
+                GLib.source_remove(timeout)
+                return self._rx_data
+
+            if self._wait_timed_out:
+                raise TimeoutError('waiting for wpas event timed out')
+
+    def _event_parse(self, line):
+        # Unescape event parameter values in '', other escaping rules not implemented
+        key = None
+        value = ''
+        count = 0
+        quoted = False
+        event = {}
+
+        def handle_eow():
+            nonlocal key, value, count, event
+            if count == 0:
+                if key is not None or not value:
+                    raise Exception('Bad event name')
+                key = 'event'
+            elif key is None:
+                if not value:
+                    return
+                key = 'arg' + str(count)
+            event[key] = value
+            key = None
+            value = ''
+            count += 1
+
+        for ch in line:
+            if ch == '\'':
+                quoted = not quoted
+            elif quoted:
+                value += ch
+            elif ch == '=' and key is None:
+                key = value
+                value = ''
+            elif ch in ' \n':
+                handle_eow()
+            else:
+                value += ch
+        handle_eow()
+        return event
+
+    def _handle_data_in(self, sock, *args):
+        newdata = sock.recv(4096)
+        if len(newdata) == 0:
+            raise Exception('Wpa_s control socket error')
+
+        decoded = newdata.decode('utf-8')
+        if len(decoded) >= 3 and decoded[0] == '<' and decoded[2] == '>':
+            decoded = decoded[3:]
+        while len(decoded) and decoded[-1] == '\n':
+            decoded = decoded[:-1]
+
+        self._rx_data.append(decoded)
+
+        event = self._event_parse(decoded)
+        if event['event'] == 'P2P-DEVICE-FOUND':
+            event.pop('event')
+            event.pop('arg1')
+            self.p2p_peers[event['p2p_dev_addr']] = event
+        elif event['event'] == 'P2P-DEVICE-LOST':
+            del self.p2p_peers[event['p2p_dev_addr']]
+        elif event['event'] == 'P2P-GO-NEG-REQUEST':
+            event.pop('event')
+            event['p2p_dev_addr'] = event.pop('arg1')
+            self.p2p_go_neg_requests[event['p2p_dev_addr']] = event
+        elif event['event'] == 'P2P-GO-NEG-SUCCESS':
+            event.pop('event')
+            addr = event.pop('peer_dev')
+            event['success'] = True
+            event['p2p_dev_addr'] = addr
+
+            if addr in self.p2p_go_neg_requests:
+                self.p2p_go_neg_requests[addr].update(event)
+            else:
+                self.p2p_go_neg_requests[addr] = event
+        elif event['event'] == 'AP-STA-CONNECTED':
+            event.pop('event')
+            addr = event.pop('arg1')
+            self.p2p_clients[addr] = event
+        elif event['event'] == 'AP-STA-DISCONNECTED':
+            addr = event.pop('arg1')
+            del self.p2p_clients[addr]
+        elif event['event'] == 'P2P-GROUP-STARTED':
+            event.pop('event')
+            event['ifname'] = event.pop('arg1')
+            self.p2p_group = event
+        elif event['event'] == 'P2P-GROUP-REMOVED':
+            self.p2p_group = None
+
+        return True
+
+    def _ctrl_request(self, command, timeout=10):
+        if type(command) is str:
+            command = str.encode(command)
+
+        self.ctrl_sock.send(bytes(command))
+
+    # Normal find phase with listen and active scan states
+    def p2p_find(self):
+        self._rx_data = []
+        self._ctrl_request('P2P_SET disc_int 2 3 300')
+        self.wait_for_event('OK')
+        self._rx_data = []
+        self._ctrl_request('P2P_FIND type=social')
+        self.wait_for_event('OK')
+
+    # Like p2p_find but uses only listen states
+    def p2p_listen(self):
+        self._rx_data = []
+        self._ctrl_request('P2P_LISTEN')
+        self.wait_for_event('OK')
+
+    # Stop a p2p_find or p2p_listen
+    def p2p_stop_find_listen(self):
+        self._rx_data = []
+        self._ctrl_request('P2P_STOP_FIND')
+        self.wait_for_event('OK')
+
+    def p2p_connect(self, peer, pin=None, go_intent=None):
+        self._rx_data = []
+        self._ctrl_request('P2P_CONNECT ' + peer['p2p_dev_addr'] + ' ' + ('pbc' if pin is None else pin) +
+                        ('' if go_intent is None else 'go_intent=' + str(go_intent)))
+        self.wait_for_event('OK')
+
+    def p2p_accept_go_neg_request(self, request, pin=None, go_intent=None):
+        self._rx_data = []
+        self._ctrl_request('P2P_CONNECT ' + request['p2p_dev_addr'] + ' ' + ('pbc' if pin is None else pin) +
+                        ('' if go_intent is None else 'go_intent=' + str(go_intent)))
+        self.wait_for_event('OK')
+
+    # Pre-accept the next GO Negotiation Request from this peer to avoid the extra Respone + Request frames
+    def p2p_authorize(self, peer, pin=None, go_intent=None):
+        self._rx_data = []
+        self._ctrl_request('P2P_CONNECT ' + peer['p2p_dev_addr'] + ' ' + ('pbc' if pin is None else pin) +
+                        ('' if go_intent is None else 'go_intent=' + str(go_intent)) + ' auth')
+        self.wait_for_event('OK')
+
+    # Probably needed: remove references to self so that the GC can call __del__ automatically
+    def clean_up(self):
+        if self.io_watch is not None:
+            GLib.source_remove(self.io_watch)
+            self.io_watch = None
+        if self.wpa_supplicant is not None:
+            ctx.stop_process(self.wpa_supplicant)
+            self.wpa_supplicant = None
+
+    def _stop_wpas(self):
+        self.clean_up()
+        if self.ctrl_sock:
+            self.ctrl_sock.close()
+            self.ctrl_sock = None
+        if os.path.exists(self.remote_ctrl):
+            os.remove(self.remote_ctrl)
+        if os.path.exists(self.local_ctrl):
+            os.remove(self.local_ctrl)
+
+    def __del__(self):
+        self._stop_wpas()
-- 
2.25.1

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 4/5] autotests: Move some variables from IWD class to instance
  2020-10-09 18:08 [PATCH 1/5] test-runner: Reserve radios for wpa_supplicant Andrew Zaborowski
  2020-10-09 18:08 ` [PATCH 2/5] autotests: Basic P2P python API Andrew Zaborowski
  2020-10-09 18:08 ` [PATCH 3/5] autotests: Add basic wpa_supplicant P2P python wrapper Andrew Zaborowski
@ 2020-10-09 18:08 ` Andrew Zaborowski
  2020-10-09 18:08 ` [PATCH 5/5] autotests: Add testP2P Andrew Zaborowski
  2020-10-19 22:22 ` [PATCH 1/5] test-runner: Reserve radios for wpa_supplicant Denis Kenzior
  4 siblings, 0 replies; 6+ messages in thread
From: Andrew Zaborowski @ 2020-10-09 18:08 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 830 bytes --]

These variables were probably meant as instance members and not class
members.
---
 autotests/util/iwd.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/autotests/util/iwd.py b/autotests/util/iwd.py
index 7e3676d9..94a118ce 100755
--- a/autotests/util/iwd.py
+++ b/autotests/util/iwd.py
@@ -231,9 +231,12 @@ class Device(IWDDBusAbstract):
         with its properties and methods
     '''
     _iface_name = IWD_DEVICE_INTERFACE
-    _wps_manager_if = None
-    _station_if = None
-    _station_props = None
+
+    def __init__(self, *args, **kwargs):
+        self._wps_manager_if = None
+        self._station_if = None
+        self._station_props = None
+        IWDDBusAbstract.__init__(self, *args, **kwargs)
 
     @property
     def _wps_manager(self):
-- 
2.25.1

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 5/5] autotests: Add testP2P
  2020-10-09 18:08 [PATCH 1/5] test-runner: Reserve radios for wpa_supplicant Andrew Zaborowski
                   ` (2 preceding siblings ...)
  2020-10-09 18:08 ` [PATCH 4/5] autotests: Move some variables from IWD class to instance Andrew Zaborowski
@ 2020-10-09 18:08 ` Andrew Zaborowski
  2020-10-19 22:22 ` [PATCH 1/5] test-runner: Reserve radios for wpa_supplicant Denis Kenzior
  4 siblings, 0 replies; 6+ messages in thread
From: Andrew Zaborowski @ 2020-10-09 18:08 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 6248 bytes --]

Add two P2P client connection scenarios.
---
 autotests/testP2P/client_test.py | 106 +++++++++++++++++++++++++++++++
 autotests/testP2P/dhcpd.conf     |   4 ++
 autotests/testP2P/hw.conf        |   6 ++
 autotests/testP2P/main.conf      |   6 ++
 autotests/testP2P/rad1-p2p.conf  |  10 +++
 5 files changed, 132 insertions(+)
 create mode 100644 autotests/testP2P/client_test.py
 create mode 100644 autotests/testP2P/dhcpd.conf
 create mode 100644 autotests/testP2P/hw.conf
 create mode 100644 autotests/testP2P/main.conf
 create mode 100644 autotests/testP2P/rad1-p2p.conf

diff --git a/autotests/testP2P/client_test.py b/autotests/testP2P/client_test.py
new file mode 100644
index 00000000..4994de76
--- /dev/null
+++ b/autotests/testP2P/client_test.py
@@ -0,0 +1,106 @@
+#!/usr/bin/python3
+
+import unittest
+import sys
+import netifaces
+import os
+
+import iwd
+from iwd import IWD
+import testutil
+from config import ctx
+from wpas import Wpas
+
+class Test(unittest.TestCase):
+    def test_1_client_go_neg_responder(self):
+        self.p2p_client_test(False)
+
+    def test_2_client_go_neg_initiator(self):
+        self.p2p_client_test(True)
+
+    def p2p_client_test(self, preauthorize):
+        wpas = Wpas(p2p=True)
+        wd = IWD()
+
+        # Not strictly necessary but prevents the station interface from queuing its scans
+        # in the wiphy radio work queue and delaying P2P scans.
+        wd.list_devices(1)[0].disconnect()
+
+        devices = wd.list_p2p_devices(1)
+        p2p = devices[0]
+        p2p.enabled = True
+        p2p.name = 'testdev1'
+
+        wpas.p2p_find()
+        p2p.discovery_request = True
+        wd.wait(5)
+        wd.wait_for_object_condition(wpas, 'len(obj.p2p_peers) == 1', max_wait=20)
+        p2p.discovery_request = False
+        wpas.p2p_listen()
+
+        peers = p2p.get_peers()
+        self.assertEqual(len(peers), 1)
+        peer = next(iter(peers.values()))
+        self.assertEqual(peer.name, wpas.config['device_name'])
+        self.assertEqual(peer.category, 'display')
+        self.assertEqual(peer.subcategory, 'monitor')
+
+        wpas_peer = next(iter(wpas.p2p_peers.values()))
+        self.assertEqual(wpas_peer['name'], p2p.name)
+        self.assertEqual(wpas_peer['pri_dev_type'], '1-0050F204-6') # 1 == Computer, 6 == Desktop
+        self.assertEqual(wpas_peer['config_methods'], '0x1080')
+
+        if preauthorize:
+            wpas.p2p_authorize(wpas_peer)
+
+        peer.connect(wait=False)
+
+        self.assertEqual(len(wpas.p2p_go_neg_requests), 0)
+        self.assertEqual(len(wpas.p2p_clients), 0)
+        wd.wait_for_object_condition(wpas, 'len(obj.p2p_go_neg_requests) == 1', max_wait=3)
+        request = wpas.p2p_go_neg_requests[wpas_peer['p2p_dev_addr']]
+
+        if not preauthorize:
+            self.assertEqual(request['dev_passwd_id'], '4')
+            self.assertEqual(request['go_intent'], '2') # Hardcoded in src/p2p.c
+
+            wpas.p2p_accept_go_neg_request(request)
+
+        wd.wait_for_object_condition(request, '\'success\' in obj', max_wait=3)
+        self.assertEqual(request['success'], True)
+        self.assertEqual(request['role'], 'GO')
+        self.assertEqual(request['wps_method'], 'PBC')
+        self.assertEqual(request['p2p_dev_addr'], wpas_peer['p2p_dev_addr'])
+
+        wd.wait_for_object_condition(wpas, 'obj.p2p_group is not None', max_wait=3)
+        go_ifname = wpas.p2p_group['ifname']
+        ctx.start_process(['ifconfig', go_ifname, '192.168.1.20', 'netmask', '255.255.255.0'], wait=True)
+        os.system('> /tmp/dhcpd.leases')
+        dhcpd = ctx.start_process(['dhcpd', '-f', '-cf', '/tmp/dhcpd.conf', '-lf', '/tmp/dhcpd.leases', go_ifname])
+
+        wd.wait_for_object_condition(wpas, 'len(obj.p2p_clients) == 1', max_wait=3)
+        client = wpas.p2p_clients[request['peer_iface']]
+        self.assertEqual(client['p2p_dev_addr'], wpas_peer['p2p_dev_addr'])
+
+        wd.wait_for_object_condition(peer, 'obj.connected', max_wait=15)
+        our_ip = netifaces.ifaddresses(peer.connected_interface)[netifaces.AF_INET][0]['addr']
+        self.assertEqual(peer.connected_ip, '192.168.1.20')
+        self.assertEqual(our_ip, '192.168.1.30')
+
+        testutil.test_iface_operstate(peer.connected_interface)
+        testutil.test_ifaces_connected(peer.connected_interface, go_ifname)
+
+        peer.disconnect()
+        wd.wait_for_object_condition(wpas, 'len(obj.p2p_clients) == 0', max_wait=3)
+        self.assertEqual(peer.connected, False)
+
+        p2p.enabled = False
+        ctx.stop_process(dhcpd)
+        wpas.clean_up()
+
+    @classmethod
+    def tearDownClass(cls):
+        IWD.clear_storage()
+
+if __name__ == '__main__':
+    unittest.main(exit=True)
diff --git a/autotests/testP2P/dhcpd.conf b/autotests/testP2P/dhcpd.conf
new file mode 100644
index 00000000..a5ef0b4a
--- /dev/null
+++ b/autotests/testP2P/dhcpd.conf
@@ -0,0 +1,4 @@
+subnet 192.168.1.0 netmask 255.255.255.0
+ {
+  range 192.168.1.30;
+ }
diff --git a/autotests/testP2P/hw.conf b/autotests/testP2P/hw.conf
new file mode 100644
index 00000000..d8f213f7
--- /dev/null
+++ b/autotests/testP2P/hw.conf
@@ -0,0 +1,6 @@
+[SETUP]
+num_radios=2
+hwsim_medium=no
+
+[WPA_SUPPLICANT]
+rad1=rad1-p2p.conf
diff --git a/autotests/testP2P/main.conf b/autotests/testP2P/main.conf
new file mode 100644
index 00000000..75040f78
--- /dev/null
+++ b/autotests/testP2P/main.conf
@@ -0,0 +1,6 @@
+[General]
+EnableNetworkConfiguration=true
+
+[P2P]
+# Something different from the default of 'pc' for validation
+DeviceType=desktop
diff --git a/autotests/testP2P/rad1-p2p.conf b/autotests/testP2P/rad1-p2p.conf
new file mode 100644
index 00000000..a34f3d91
--- /dev/null
+++ b/autotests/testP2P/rad1-p2p.conf
@@ -0,0 +1,10 @@
+ctrl_interface=/tmp/rad1-p2p-wpas
+update_config=0
+device_name=testdev2
+device_type=7-0050F204-4 # 7 == Display, 4 == Monitor
+config_methods=virtual_push_button
+p2p_listen_reg_class=81
+p2p_listen_channel=11
+p2p_oper_reg_class=81
+p2p_oper_channel=11
+p2p_go_intent=10
-- 
2.25.1

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/5] test-runner: Reserve radios for wpa_supplicant
  2020-10-09 18:08 [PATCH 1/5] test-runner: Reserve radios for wpa_supplicant Andrew Zaborowski
                   ` (3 preceding siblings ...)
  2020-10-09 18:08 ` [PATCH 5/5] autotests: Add testP2P Andrew Zaborowski
@ 2020-10-19 22:22 ` Denis Kenzior
  4 siblings, 0 replies; 6+ messages in thread
From: Denis Kenzior @ 2020-10-19 22:22 UTC (permalink / raw)
  To: iwd

[-- Attachment #1: Type: text/plain, Size: 547 bytes --]

Hi Andrew,

On 10/9/20 1:08 PM, Andrew Zaborowski wrote:
> Add support for a WPA_SUPPLICANT section in  hw.conf where
> 'radN=<config_path>' lines will only reserve radios and create
> interfaces for the autotest to be able to start wpa_supplicant on them,
> i.e. this prevents iwd or hostapd from being started on them but doesn't
> start a wpa_supplicant instance by itself.
> ---
>   tools/test-runner | 24 +++++++++++++++++-------
>   1 file changed, 17 insertions(+), 7 deletions(-)
> 

All applied, thanks.

Regards,
-Denis

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2020-10-19 22:22 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-09 18:08 [PATCH 1/5] test-runner: Reserve radios for wpa_supplicant Andrew Zaborowski
2020-10-09 18:08 ` [PATCH 2/5] autotests: Basic P2P python API Andrew Zaborowski
2020-10-09 18:08 ` [PATCH 3/5] autotests: Add basic wpa_supplicant P2P python wrapper Andrew Zaborowski
2020-10-09 18:08 ` [PATCH 4/5] autotests: Move some variables from IWD class to instance Andrew Zaborowski
2020-10-09 18:08 ` [PATCH 5/5] autotests: Add testP2P Andrew Zaborowski
2020-10-19 22:22 ` [PATCH 1/5] test-runner: Reserve radios for wpa_supplicant Denis Kenzior

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.