2020-10-09 20:08:53 +02:00
|
|
|
#!/usr/bin/python3
|
|
|
|
import os
|
|
|
|
import socket
|
2022-06-02 20:49:55 +02:00
|
|
|
import shutil
|
2020-10-09 20:08:53 +02:00
|
|
|
from gi.repository import GLib
|
|
|
|
from config import ctx
|
2022-06-02 20:49:55 +02:00
|
|
|
from unittest import SkipTest
|
2022-04-06 21:14:52 +02:00
|
|
|
|
2022-01-04 19:18:56 +01:00
|
|
|
import binascii
|
2020-10-09 20:08:53 +02:00
|
|
|
|
2022-04-06 21:14:52 +02:00
|
|
|
from utils import Process
|
|
|
|
|
2020-10-09 20:08:53 +02:00
|
|
|
ctrl_count = 0
|
|
|
|
|
|
|
|
class Wpas:
|
2022-06-02 20:49:55 +02:00
|
|
|
io_watch = None
|
|
|
|
sockets = {}
|
|
|
|
wpa_supplicant = None
|
|
|
|
cleanup_paths = []
|
|
|
|
|
2020-10-09 20:08:53 +02:00
|
|
|
def _start_wpas(self, config_name=None, p2p=False):
|
2022-06-02 20:49:55 +02:00
|
|
|
if not shutil.which('wpa_supplicant'):
|
|
|
|
print('wpa_supplicant not found, skipping test')
|
|
|
|
raise SkipTest
|
|
|
|
|
2020-10-09 20:08:53 +02:00
|
|
|
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:
|
2021-06-04 03:50:51 +02:00
|
|
|
raise Exception('More than one wpa_supplicant interface matches given config')
|
2020-10-09 20:08:53 +02:00
|
|
|
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']
|
|
|
|
|
|
|
|
cmd = ['wpa_supplicant', '-i', self.interface.name, '-c', self.config_path]
|
2022-04-06 21:14:52 +02:00
|
|
|
if Process.is_verbose('wpa_supplicant-dbg'):
|
2020-10-09 20:08:53 +02:00
|
|
|
cmd += ['-d']
|
|
|
|
|
|
|
|
self.wpa_supplicant = ctx.start_process(cmd)
|
|
|
|
|
2021-06-04 03:50:51 +02:00
|
|
|
self.sockets = {}
|
|
|
|
self.io_watch = GLib.io_add_watch(self._get_socket(), GLib.IO_IN, self._handle_data_in)
|
2020-10-09 20:08:53 +02:00
|
|
|
|
|
|
|
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])
|
|
|
|
|
2022-01-12 01:55:49 +01:00
|
|
|
def _check_event(self, event):
|
|
|
|
if not event and len(self._rx_data) >= 1:
|
|
|
|
return self._rx_data[0]
|
2020-10-09 20:08:53 +02:00
|
|
|
|
2022-01-12 01:55:49 +01:00
|
|
|
for e in self._rx_data:
|
|
|
|
if event in e:
|
2023-11-13 15:32:54 +01:00
|
|
|
return e
|
2020-10-09 20:08:53 +02:00
|
|
|
|
2022-01-12 01:55:49 +01:00
|
|
|
return False
|
2022-01-04 19:18:54 +01:00
|
|
|
|
2022-01-12 01:55:49 +01:00
|
|
|
def wait_for_event(self, event, timeout=10):
|
|
|
|
self._rx_data = []
|
|
|
|
return ctx.non_block_wait(self._check_event, timeout, event)
|
2020-10-09 20:08:53 +02:00
|
|
|
|
2022-01-04 19:18:54 +01:00
|
|
|
def wait_for_result(self, timeout=10):
|
|
|
|
self._rx_data = []
|
|
|
|
return self.wait_for_event(None, timeout=timeout)
|
|
|
|
|
2020-10-09 20:08:53 +02:00
|
|
|
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:
|
|
|
|
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')
|
2021-08-23 16:14:24 +02:00
|
|
|
event['role'] = event.pop('arg2')
|
2020-10-09 20:08:53 +02:00
|
|
|
self.p2p_group = event
|
|
|
|
elif event['event'] == 'P2P-GROUP-REMOVED':
|
|
|
|
self.p2p_group = None
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
2021-06-04 03:50:51 +02:00
|
|
|
def _ctrl_request(self, command, ifname=None):
|
2020-10-09 20:08:53 +02:00
|
|
|
if type(command) is str:
|
|
|
|
command = str.encode(command)
|
|
|
|
|
2021-06-04 03:50:51 +02:00
|
|
|
self._get_socket(ifname).send(bytes(command))
|
|
|
|
|
|
|
|
def _get_socket(self, ifname=None):
|
|
|
|
global ctrl_count
|
|
|
|
|
|
|
|
if ifname is None:
|
|
|
|
ifname = self.ifname
|
|
|
|
|
|
|
|
if ifname in self.sockets:
|
|
|
|
return self.sockets[ifname]
|
|
|
|
|
|
|
|
local_path = '/tmp/wpas_' + str(os.getpid()) + '_' + str(ctrl_count)
|
|
|
|
ctrl_count = ctrl_count + 1
|
|
|
|
remote_path = self.socket_path + '/' + ifname
|
|
|
|
|
|
|
|
self.wpa_supplicant.wait_for_socket(remote_path, 2)
|
|
|
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
|
|
|
sock.bind(local_path)
|
|
|
|
self.cleanup_paths.append(local_path)
|
|
|
|
sock.connect(remote_path)
|
|
|
|
self.cleanup_paths.append(remote_path)
|
|
|
|
|
|
|
|
self.sockets[ifname] = sock
|
|
|
|
return sock
|
2020-10-09 20:08:53 +02:00
|
|
|
|
|
|
|
# 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) +
|
2021-06-04 03:50:50 +02:00
|
|
|
('' if go_intent is None else ' go_intent=' + str(go_intent)))
|
2020-10-09 20:08:53 +02:00
|
|
|
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) +
|
2021-06-04 03:50:50 +02:00
|
|
|
('' if go_intent is None else ' go_intent=' + str(go_intent)))
|
2020-10-09 20:08:53 +02:00
|
|
|
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) +
|
2021-06-04 03:50:50 +02:00
|
|
|
('' if go_intent is None else ' go_intent=' + str(go_intent)) + ' auth')
|
2020-10-09 20:08:53 +02:00
|
|
|
self.wait_for_event('OK')
|
|
|
|
|
2021-06-04 03:50:51 +02:00
|
|
|
def p2p_set(self, key, value, **kwargs):
|
|
|
|
self._ctrl_request('P2P_SET ' + key + ' ' + value, **kwargs)
|
|
|
|
|
|
|
|
def set(self, key, value, **kwargs):
|
|
|
|
self._ctrl_request('SET ' + key + ' ' + value, **kwargs)
|
|
|
|
|
2022-02-22 23:28:45 +01:00
|
|
|
def dpp_enrollee_start(self, uri=None, oper_and_channel=None):
|
|
|
|
if not oper_and_channel:
|
|
|
|
oper_and_channel = '81/1'
|
|
|
|
|
2022-01-12 01:55:50 +01:00
|
|
|
self._rx_data = []
|
2022-02-22 23:28:45 +01:00
|
|
|
self._ctrl_request('DPP_BOOTSTRAP_GEN type=qrcode chan=%s' % oper_and_channel)
|
|
|
|
self._dpp_qr_id = self.wait_for_result()
|
|
|
|
self._ctrl_request('DPP_BOOTSTRAP_GET_URI %s' % self._dpp_qr_id)
|
|
|
|
self._dpp_uri = self.wait_for_result()
|
|
|
|
|
|
|
|
print("DPP Enrollee QR: %s" % self._dpp_uri)
|
2022-01-04 19:18:56 +01:00
|
|
|
|
|
|
|
if uri:
|
2022-01-12 01:55:50 +01:00
|
|
|
self._rx_data = []
|
2022-01-04 19:18:56 +01:00
|
|
|
self._ctrl_request('DPP_QR_CODE ' + uri)
|
|
|
|
self._dpp_qr_id = self.wait_for_result()
|
2022-01-12 01:55:50 +01:00
|
|
|
self._rx_data = []
|
2022-01-04 19:18:56 +01:00
|
|
|
self._ctrl_request('DPP_AUTH_INIT peer=%s role=enrollee' % self._dpp_qr_id)
|
2022-02-22 23:28:45 +01:00
|
|
|
else:
|
|
|
|
self._ctrl_request('DPP_CHIRP own=%s iter=100' % self._dpp_qr_id)
|
|
|
|
|
|
|
|
return self._dpp_uri
|
2022-01-04 19:18:56 +01:00
|
|
|
|
2023-11-08 18:21:53 +01:00
|
|
|
def dpp_configurator_create(self, uri=None):
|
2022-01-12 01:55:50 +01:00
|
|
|
self._rx_data = []
|
2022-01-04 19:18:56 +01:00
|
|
|
self._ctrl_request('DPP_CONFIGURATOR_ADD')
|
|
|
|
self._dpp_conf_id = self.wait_for_result()
|
2022-01-12 01:55:50 +01:00
|
|
|
while not self._dpp_conf_id.isnumeric():
|
|
|
|
self._dpp_conf_id = self.wait_for_result()
|
|
|
|
|
2023-11-08 18:21:53 +01:00
|
|
|
if not uri:
|
2023-11-13 15:32:54 +01:00
|
|
|
print("DPP Configurator ID: %s" % self._dpp_conf_id)
|
2023-11-08 18:21:53 +01:00
|
|
|
return
|
|
|
|
|
2022-01-12 01:55:50 +01:00
|
|
|
self._rx_data = []
|
2022-01-04 19:18:56 +01:00
|
|
|
self._ctrl_request('DPP_QR_CODE ' + uri)
|
|
|
|
self._dpp_qr_id = self.wait_for_result()
|
2022-01-12 01:55:50 +01:00
|
|
|
while not self._dpp_conf_id.isnumeric():
|
|
|
|
self._dpp_qr_id = self.wait_for_result()
|
2022-01-04 19:18:56 +01:00
|
|
|
|
|
|
|
print("DPP Configurator ID: %s. DPP QR ID: %s" % (self._dpp_conf_id, self._dpp_qr_id))
|
|
|
|
|
2022-01-12 01:55:56 +01:00
|
|
|
def dpp_configurator_start(self, ssid, passphrase, freq=None):
|
2022-01-04 19:18:56 +01:00
|
|
|
ssid = binascii.hexlify(ssid.encode()).decode()
|
|
|
|
passphrase = binascii.hexlify(passphrase.encode()).decode()
|
|
|
|
|
2022-01-12 01:55:56 +01:00
|
|
|
cmd = 'DPP_AUTH_INIT peer=%s conf=sta-psk ssid=%s pass=%s ' % (self._dpp_qr_id, ssid, passphrase)
|
|
|
|
|
|
|
|
if freq:
|
|
|
|
cmd += 'neg_freq=%u ' % freq
|
|
|
|
|
|
|
|
self._rx_data = []
|
|
|
|
self._ctrl_request(cmd)
|
2022-01-12 19:08:43 +01:00
|
|
|
self.wait_for_event('DPP-AUTH-SUCCESS', timeout=30)
|
2022-01-04 19:18:56 +01:00
|
|
|
self.wait_for_event('DPP-CONF-SENT')
|
|
|
|
|
2023-11-08 18:21:53 +01:00
|
|
|
def dpp_bootstrap_gen(self, type='qrcode', curve=None):
|
|
|
|
cmd = f'DPP_BOOTSTRAP_GEN type={type}'
|
|
|
|
|
|
|
|
if curve:
|
|
|
|
cmd += f' curve={curve}'
|
|
|
|
|
|
|
|
self._rx_data = []
|
|
|
|
self._ctrl_request(cmd)
|
|
|
|
self._dpp_id = self.wait_for_result()
|
2023-11-13 15:32:54 +01:00
|
|
|
while not self._dpp_id.isnumeric():
|
|
|
|
self._dpp_id = self.wait_for_result()
|
2023-11-08 18:21:53 +01:00
|
|
|
|
|
|
|
def dpp_pkex_add(self, code, identifier=None, version=None, initiator=False, role=None):
|
|
|
|
cmd = f'DPP_PKEX_ADD own={self._dpp_id}'
|
|
|
|
|
|
|
|
if identifier:
|
|
|
|
cmd += f' identifier={identifier}'
|
|
|
|
|
|
|
|
if initiator:
|
|
|
|
cmd += f' init=1'
|
|
|
|
|
|
|
|
if version:
|
|
|
|
cmd += f' ver={version}'
|
|
|
|
|
|
|
|
if role:
|
|
|
|
cmd += f' role={role}'
|
|
|
|
|
|
|
|
cmd += f' code={code}'
|
|
|
|
|
|
|
|
self._rx_data = []
|
|
|
|
self._ctrl_request(cmd)
|
|
|
|
|
2023-11-13 15:32:54 +01:00
|
|
|
def dpp_pkex_remove(self):
|
|
|
|
self._rx_data = []
|
|
|
|
self._ctrl_request("DPP_PKEX_REMOVE *")
|
|
|
|
|
2023-11-08 18:21:53 +01:00
|
|
|
def dpp_listen(self, freq):
|
|
|
|
self._rx_data = []
|
|
|
|
self._ctrl_request(f'DPP_LISTEN {freq}')
|
|
|
|
|
2023-11-13 15:32:54 +01:00
|
|
|
def dpp_stop_listen(self):
|
|
|
|
self._rx_data = []
|
|
|
|
self._ctrl_request("DPP_STOP_LISTEN")
|
|
|
|
|
2022-01-12 01:55:57 +01:00
|
|
|
def dpp_configurator_remove(self):
|
|
|
|
self._ctrl_request('DPP_CONFIGURATOR_REMOVE *')
|
|
|
|
self.wait_for_result()
|
|
|
|
self._ctrl_request('DPP_BOOTSTRAP_REMOVE *')
|
|
|
|
self.wait_for_result()
|
|
|
|
|
2022-01-12 19:08:44 +01:00
|
|
|
def disconnect(self):
|
|
|
|
self._ctrl_request('DISCONNECT')
|
|
|
|
|
2020-10-09 20:08:53 +02:00
|
|
|
# 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
|
2021-06-10 23:03:52 +02:00
|
|
|
for ifname in self.sockets:
|
|
|
|
self.sockets[ifname].close()
|
|
|
|
self.sockets = {}
|
2020-10-09 20:08:53 +02:00
|
|
|
if self.wpa_supplicant is not None:
|
|
|
|
ctx.stop_process(self.wpa_supplicant)
|
|
|
|
self.wpa_supplicant = None
|
2021-06-04 03:50:51 +02:00
|
|
|
for path in self.cleanup_paths:
|
|
|
|
if os.path.exists(path):
|
|
|
|
os.remove(path)
|
|
|
|
self.cleanup_paths = []
|
2020-10-09 20:08:53 +02:00
|
|
|
|
|
|
|
def __del__(self):
|
2021-06-10 23:03:52 +02:00
|
|
|
self.clean_up()
|