2014-08-05 17:25:59 +02:00
|
|
|
# FST tests related classes
|
|
|
|
# Copyright (c) 2015, Qualcomm Atheros, Inc.
|
|
|
|
#
|
|
|
|
# This software may be distributed under the terms of the BSD license.
|
|
|
|
# See README for more details.
|
|
|
|
|
|
|
|
import logging
|
|
|
|
import subprocess
|
|
|
|
import os
|
|
|
|
import signal
|
|
|
|
import time
|
|
|
|
import re
|
|
|
|
|
|
|
|
import hostapd
|
|
|
|
import wpaspy
|
|
|
|
import utils
|
|
|
|
from wpasupplicant import WpaSupplicant
|
|
|
|
|
|
|
|
import fst_test_common
|
|
|
|
|
|
|
|
logger = logging.getLogger()
|
|
|
|
|
|
|
|
def parse_fst_iface_event(ev):
|
|
|
|
"""Parses FST iface event that comes as a string, e.g.
|
|
|
|
"<3>FST-EVENT-IFACE attached ifname=wlan9 group=fstg0"
|
|
|
|
Returns a dictionary with parsed "event_type", "ifname", and "group"; or
|
|
|
|
None if not an FST event or can't be parsed."""
|
|
|
|
event = {}
|
|
|
|
if ev.find("FST-EVENT-IFACE") == -1:
|
|
|
|
return None
|
|
|
|
if ev.find("attached") != -1:
|
|
|
|
event['event_type'] = 'attached'
|
|
|
|
elif ev.find("detached") != -1:
|
|
|
|
event['event_type'] = 'detached'
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
f = re.search("ifname=(\S+)", ev)
|
|
|
|
if f is not None:
|
|
|
|
event['ifname'] = f.group(1)
|
|
|
|
f = re.search("group=(\S+)", ev)
|
|
|
|
if f is not None:
|
|
|
|
event['group'] = f.group(1)
|
|
|
|
return event
|
|
|
|
|
|
|
|
def parse_fst_session_event(ev):
|
|
|
|
"""Parses FST session event that comes as a string, e.g.
|
|
|
|
"<3>FST-EVENT-SESSION event_type=EVENT_FST_SESSION_STATE session_id=0 reason=REASON_STT"
|
|
|
|
Returns a dictionary with parsed "type", "id", and "reason"; or None if not
|
|
|
|
a FST event or can't be parsed"""
|
|
|
|
event = {}
|
|
|
|
if ev.find("FST-EVENT-SESSION") == -1:
|
|
|
|
return None
|
|
|
|
event['new_state'] = '' # The field always exists in the dictionary
|
|
|
|
f = re.search("event_type=(\S+)", ev)
|
|
|
|
if f is None:
|
|
|
|
return None
|
|
|
|
event['type'] = f.group(1)
|
|
|
|
f = re.search("session_id=(\d+)", ev)
|
|
|
|
if f is not None:
|
|
|
|
event['id'] = f.group(1)
|
|
|
|
f = re.search("old_state=(\S+)", ev)
|
|
|
|
if f is not None:
|
|
|
|
event['old_state'] = f.group(1)
|
|
|
|
f = re.search("new_state=(\S+)", ev)
|
|
|
|
if f is not None:
|
|
|
|
event['new_state'] = f.group(1)
|
|
|
|
f = re.search("reason=(\S+)", ev)
|
|
|
|
if f is not None:
|
|
|
|
event['reason'] = f.group(1)
|
|
|
|
return event
|
|
|
|
|
2015-06-09 20:31:09 +02:00
|
|
|
def start_two_ap_sta_pairs(apdev, rsn=False):
|
2014-08-05 17:25:59 +02:00
|
|
|
"""auxiliary function that creates two pairs of APs and STAs"""
|
|
|
|
ap1 = FstAP(apdev[0]['ifname'], 'fst_11a', 'a',
|
|
|
|
fst_test_common.fst_test_def_chan_a,
|
|
|
|
fst_test_common.fst_test_def_group,
|
|
|
|
fst_test_common.fst_test_def_prio_low,
|
2015-06-09 20:31:09 +02:00
|
|
|
fst_test_common.fst_test_def_llt, rsn=rsn)
|
2014-08-05 17:25:59 +02:00
|
|
|
ap1.start()
|
|
|
|
ap2 = FstAP(apdev[1]['ifname'], 'fst_11g', 'g',
|
|
|
|
fst_test_common.fst_test_def_chan_g,
|
|
|
|
fst_test_common.fst_test_def_group,
|
|
|
|
fst_test_common.fst_test_def_prio_high,
|
2015-06-09 20:31:09 +02:00
|
|
|
fst_test_common.fst_test_def_llt, rsn=rsn)
|
2014-08-05 17:25:59 +02:00
|
|
|
ap2.start()
|
|
|
|
|
|
|
|
sta1 = FstSTA('wlan5',
|
|
|
|
fst_test_common.fst_test_def_group,
|
|
|
|
fst_test_common.fst_test_def_prio_low,
|
2015-06-09 20:31:09 +02:00
|
|
|
fst_test_common.fst_test_def_llt, rsn=rsn)
|
2014-08-05 17:25:59 +02:00
|
|
|
sta1.start()
|
|
|
|
sta2 = FstSTA('wlan6',
|
|
|
|
fst_test_common.fst_test_def_group,
|
|
|
|
fst_test_common.fst_test_def_prio_high,
|
2015-06-09 20:31:09 +02:00
|
|
|
fst_test_common.fst_test_def_llt, rsn=rsn)
|
2014-08-05 17:25:59 +02:00
|
|
|
sta2.start()
|
|
|
|
|
|
|
|
return ap1, ap2, sta1, sta2
|
|
|
|
|
|
|
|
def stop_two_ap_sta_pairs(ap1, ap2, sta1, sta2):
|
|
|
|
sta1.stop()
|
|
|
|
sta2.stop()
|
|
|
|
ap1.stop()
|
|
|
|
ap2.stop()
|
|
|
|
|
2015-06-09 20:31:09 +02:00
|
|
|
def connect_two_ap_sta_pairs(ap1, ap2, dev1, dev2, rsn=False):
|
2014-08-05 17:25:59 +02:00
|
|
|
"""Connects a pair of stations, each one to a separate AP"""
|
|
|
|
dev1.scan(freq=fst_test_common.fst_test_def_freq_a)
|
|
|
|
dev2.scan(freq=fst_test_common.fst_test_def_freq_g)
|
|
|
|
|
2015-06-09 20:31:09 +02:00
|
|
|
if rsn:
|
|
|
|
dev1.connect(ap1, psk="12345678",
|
|
|
|
scan_freq=fst_test_common.fst_test_def_freq_a)
|
|
|
|
dev2.connect(ap2, psk="12345678",
|
|
|
|
scan_freq=fst_test_common.fst_test_def_freq_g)
|
|
|
|
else:
|
|
|
|
dev1.connect(ap1, key_mgmt="NONE",
|
|
|
|
scan_freq=fst_test_common.fst_test_def_freq_a)
|
|
|
|
dev2.connect(ap2, key_mgmt="NONE",
|
|
|
|
scan_freq=fst_test_common.fst_test_def_freq_g)
|
2014-08-05 17:25:59 +02:00
|
|
|
|
|
|
|
def disconnect_two_ap_sta_pairs(ap1, ap2, dev1, dev2):
|
|
|
|
dev1.disconnect()
|
|
|
|
dev2.disconnect()
|
|
|
|
|
|
|
|
def external_sta_connect(sta, ap, **kwargs):
|
|
|
|
"""Connects the external station to the given AP"""
|
|
|
|
if not isinstance(sta, WpaSupplicant):
|
|
|
|
raise Exception("Bad STA object")
|
|
|
|
if not isinstance(ap, FstAP):
|
|
|
|
raise Exception("Bad AP object to connect to")
|
|
|
|
hap = ap.get_instance()
|
|
|
|
sta.connect(ap.get_ssid(), **kwargs)
|
|
|
|
|
|
|
|
def disconnect_external_sta(sta, ap, check_disconnect=True):
|
|
|
|
"""Disconnects the external station from the AP"""
|
|
|
|
if not isinstance(sta, WpaSupplicant):
|
|
|
|
raise Exception("Bad STA object")
|
|
|
|
if not isinstance(ap, FstAP):
|
|
|
|
raise Exception("Bad AP object to connect to")
|
|
|
|
sta.request("DISCONNECT")
|
|
|
|
if check_disconnect:
|
|
|
|
hap = ap.get_instance()
|
|
|
|
ev = hap.wait_event([ "AP-STA-DISCONNECTED" ], timeout=10)
|
|
|
|
if ev is None:
|
|
|
|
raise Exception("No disconnection event received from %s" % ap.get_ssid())
|
|
|
|
|
|
|
|
#
|
|
|
|
# FstDevice class
|
|
|
|
# This is the parent class for the AP (FstAP) and STA (FstSTA) that implements
|
|
|
|
# FST functionality.
|
|
|
|
#
|
|
|
|
class FstDevice:
|
2015-06-09 20:31:09 +02:00
|
|
|
def __init__(self, iface, fst_group, fst_pri, fst_llt=None, rsn=False):
|
2014-08-05 17:25:59 +02:00
|
|
|
self.iface = iface
|
|
|
|
self.fst_group = fst_group
|
|
|
|
self.fst_pri = fst_pri
|
|
|
|
self.fst_llt = fst_llt # None llt means no llt parameter will be set
|
|
|
|
self.instance = None # Hostapd/WpaSupplicant instance
|
|
|
|
self.peer_obj = None # Peer object, must be a FstDevice child object
|
|
|
|
self.new_peer_addr = None # Peer MAC address for new session iface
|
|
|
|
self.old_peer_addr = None # Peer MAC address for old session iface
|
|
|
|
self.role = 'initiator' # Role: initiator/responder
|
|
|
|
s = self.grequest("FST-MANAGER TEST_REQUEST IS_SUPPORTED")
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise utils.HwsimSkip("FST not supported")
|
2015-06-09 20:31:09 +02:00
|
|
|
self.rsn = rsn
|
2014-08-05 17:25:59 +02:00
|
|
|
|
|
|
|
def ifname(self):
|
|
|
|
return self.iface
|
|
|
|
|
|
|
|
def get_instance(self):
|
|
|
|
"""Gets the Hostapd/WpaSupplicant instance"""
|
|
|
|
raise Exception("Virtual get_instance() called!")
|
|
|
|
|
|
|
|
def get_own_mac_address(self):
|
|
|
|
"""Gets the device's own MAC address"""
|
|
|
|
raise Exception("Virtual get_own_mac_address() called!")
|
|
|
|
|
|
|
|
def get_new_peer_addr(self):
|
|
|
|
return self.new_peer_addr
|
|
|
|
|
|
|
|
def get_old_peer_addr(self):
|
|
|
|
return self.old_peer_addr
|
|
|
|
|
|
|
|
def get_actual_peer_addr(self):
|
|
|
|
"""Gets the peer address. A connected AP/station address is returned."""
|
|
|
|
raise Exception("Virtual get_actual_peer_addr() called!")
|
|
|
|
|
|
|
|
def grequest(self, req):
|
|
|
|
"""Send request on the global control interface"""
|
|
|
|
raise Exception, "Virtual grequest() called!"
|
|
|
|
|
|
|
|
def wait_gevent(self, events, timeout=None):
|
|
|
|
"""Wait for a list of events on the global interface"""
|
|
|
|
raise Exception("Virtual wait_gevent() called!")
|
|
|
|
|
|
|
|
def request(self, req):
|
|
|
|
"""Issue a request to the control interface"""
|
|
|
|
h = self.get_instance()
|
|
|
|
return h.request(req)
|
|
|
|
|
|
|
|
def wait_event(self, events, timeout=None):
|
|
|
|
"""Wait for an event from the control interface"""
|
|
|
|
h = self.get_instance()
|
|
|
|
if timeout is not None:
|
|
|
|
return h.wait_event(events, timeout=timeout)
|
|
|
|
else:
|
|
|
|
return h.wait_event(events)
|
|
|
|
|
|
|
|
def set_old_peer_addr(self, peer_addr=None):
|
|
|
|
"""Sets the peer address"""
|
|
|
|
if peer_addr is not None:
|
|
|
|
self.old_peer_addr = peer_addr
|
|
|
|
else:
|
|
|
|
self.old_peer_addr = self.get_actual_peer_addr()
|
|
|
|
|
|
|
|
def set_new_peer_addr(self, peer_addr=None):
|
|
|
|
"""Sets the peer address"""
|
|
|
|
if peer_addr is not None:
|
|
|
|
self.new_peer_addr = peer_addr
|
|
|
|
else:
|
|
|
|
self.new_peer_addr = self.get_actual_peer_addr()
|
|
|
|
|
|
|
|
def add_peer(self, obj, old_peer_addr=None, new_peer_addr=None):
|
|
|
|
"""Add peer for FST session(s). 'obj' is a FstDevice subclass object.
|
|
|
|
The method must be called before add_session().
|
|
|
|
If peer_addr is not specified, the address of the currently connected
|
|
|
|
station is used."""
|
|
|
|
if not isinstance(obj, FstDevice):
|
|
|
|
raise Exception("Peer must be a FstDevice object")
|
|
|
|
self.peer_obj = obj
|
|
|
|
self.set_old_peer_addr(old_peer_addr)
|
|
|
|
self.set_new_peer_addr(new_peer_addr)
|
|
|
|
|
|
|
|
def get_peer(self):
|
|
|
|
"""Returns peer object"""
|
|
|
|
return self.peer_obj
|
|
|
|
|
|
|
|
def set_fst_parameters(self, group_id=None, pri=None, llt=None):
|
|
|
|
"""Change/set new FST parameters. Can be used to start FST sessions with
|
|
|
|
different FST parameters than defined in the configuration file."""
|
|
|
|
if group_id is not None:
|
|
|
|
self.fst_group = group_id
|
|
|
|
if pri is not None:
|
|
|
|
self.fst_pri = pri
|
|
|
|
if llt is not None:
|
|
|
|
self.fst_llt = llt
|
|
|
|
|
|
|
|
def get_local_mbies(self, ifname=None):
|
|
|
|
if_name = ifname if ifname is not None else self.iface
|
|
|
|
return self.grequest("FST-MANAGER TEST_REQUEST GET_LOCAL_MBIES " + if_name)
|
|
|
|
|
|
|
|
def add_session(self):
|
|
|
|
"""Adds an FST session. add_peer() must be called calling this
|
|
|
|
function"""
|
|
|
|
if self.peer_obj is None:
|
|
|
|
raise Exception("Peer wasn't added before starting session")
|
|
|
|
grp = ' ' + self.fst_group if self.fst_group != '' else ''
|
|
|
|
sid = self.grequest("FST-MANAGER SESSION_ADD" + grp)
|
|
|
|
sid = sid.strip()
|
|
|
|
if sid.startswith("FAIL"):
|
|
|
|
raise Exception("Cannot add FST session with groupid ==" + grp)
|
|
|
|
return sid
|
|
|
|
|
|
|
|
def set_session_param(self, params):
|
|
|
|
request = "FST-MANAGER SESSION_SET"
|
|
|
|
if params is not None and params != '':
|
|
|
|
request = request + ' ' + params
|
|
|
|
return self.grequest(request)
|
|
|
|
|
2015-06-09 11:19:47 +02:00
|
|
|
def get_session_params(self, sid):
|
|
|
|
request = "FST-MANAGER SESSION_GET " + sid
|
|
|
|
res = self.grequest(request)
|
|
|
|
if res.startswith("FAIL"):
|
|
|
|
return None
|
|
|
|
params = {}
|
|
|
|
for i in res.splitlines():
|
|
|
|
p = i.split('=')
|
|
|
|
params[p[0]] = p[1]
|
|
|
|
return params
|
|
|
|
|
|
|
|
def iface_peers(self, ifname):
|
|
|
|
grp = self.fst_group if self.fst_group != '' else ''
|
|
|
|
res = self.grequest("FST-MANAGER IFACE_PEERS " + grp + ' ' + ifname)
|
|
|
|
if res.startswith("FAIL"):
|
|
|
|
return None
|
|
|
|
return res.splitlines()
|
|
|
|
|
|
|
|
def get_peer_mbies(self, ifname, peer_addr):
|
|
|
|
return self.grequest("FST-MANAGER GET_PEER_MBIES %s %s" % (ifname, peer_addr))
|
|
|
|
|
|
|
|
def list_ifaces(self):
|
|
|
|
grp = self.fst_group if self.fst_group != '' else ''
|
|
|
|
res = self.grequest("FST-MANAGER LIST_IFACES " + grp)
|
|
|
|
if res.startswith("FAIL"):
|
|
|
|
return None
|
|
|
|
ifaces = []
|
|
|
|
for i in res.splitlines():
|
|
|
|
p = i.split(':')
|
|
|
|
iface = {}
|
|
|
|
iface['name'] = p[0]
|
|
|
|
iface['priority'] = p[1]
|
|
|
|
iface['llt'] = p[2]
|
|
|
|
ifaces.append(iface)
|
|
|
|
return ifaces
|
|
|
|
|
|
|
|
def list_groups(self):
|
|
|
|
res = self.grequest("FST-MANAGER LIST_GROUPS")
|
|
|
|
if res.startswith("FAIL"):
|
|
|
|
return None
|
|
|
|
return res.splitlines()
|
|
|
|
|
2014-08-05 17:25:59 +02:00
|
|
|
def configure_session(self, sid, new_iface, old_iface = None):
|
|
|
|
"""Calls session_set for a number of parameters some of which are stored
|
|
|
|
in "self" while others are passed to this function explicitly. If
|
|
|
|
old_iface is None, current iface is used; if old_iface is an empty
|
|
|
|
string."""
|
|
|
|
oldiface = old_iface if old_iface is not None else self.iface
|
|
|
|
s = self.set_session_param(sid + ' old_ifname=' + oldiface)
|
|
|
|
if not s.startswith("OK"):
|
|
|
|
raise Exception("Cannot set FST session old_ifname: " + s)
|
|
|
|
if new_iface is not None:
|
|
|
|
s = self.set_session_param(sid + " new_ifname=" + new_iface)
|
|
|
|
if not s.startswith("OK"):
|
|
|
|
raise Exception("Cannot set FST session new_ifname:" + s)
|
|
|
|
if self.new_peer_addr is not None and self.new_peer_addr != '':
|
|
|
|
s = self.set_session_param(sid + " new_peer_addr=" + self.new_peer_addr)
|
|
|
|
if not s.startswith("OK"):
|
|
|
|
raise Exception("Cannot set FST session peer address:" + s + " (new)")
|
|
|
|
if self.old_peer_addr is not None and self.old_peer_addr != '':
|
|
|
|
s = self.set_session_param(sid + " old_peer_addr=" + self.old_peer_addr)
|
|
|
|
if not s.startswith("OK"):
|
|
|
|
raise Exception("Cannot set FST session peer address:" + s + " (old)")
|
|
|
|
if self.fst_llt is not None and self.fst_llt != '':
|
|
|
|
s = self.set_session_param(sid + " llt=" + self.fst_llt)
|
|
|
|
if not s.startswith("OK"):
|
|
|
|
raise Exception("Cannot set FST session llt:" + s)
|
|
|
|
|
|
|
|
def send_iface_attach_request(self, ifname, group, llt, priority):
|
|
|
|
request = "FST-ATTACH " + ifname + ' ' + group
|
|
|
|
if llt is not None:
|
|
|
|
request += " llt=" + llt
|
|
|
|
if priority is not None:
|
|
|
|
request += " priority=" + priority
|
|
|
|
res = self.grequest(request)
|
|
|
|
if not res.startswith("OK"):
|
|
|
|
raise Exception("Cannot attach FST iface: " + res)
|
|
|
|
|
|
|
|
def send_iface_detach_request(self, ifname):
|
|
|
|
res = self.grequest("FST-DETACH " + ifname)
|
|
|
|
if not res.startswith("OK"):
|
|
|
|
raise Exception("Cannot detach FST iface: " + res)
|
|
|
|
|
|
|
|
def send_session_setup_request(self, sid):
|
|
|
|
s = self.grequest("FST-MANAGER SESSION_INITIATE " + sid)
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise Exception("Cannot send setup request: %s" % s)
|
|
|
|
return s
|
|
|
|
|
|
|
|
def send_session_setup_response(self, sid, response):
|
|
|
|
request = "FST-MANAGER SESSION_RESPOND " + sid + " " + response
|
|
|
|
s = self.grequest(request)
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise Exception("Cannot send setup response: %s" % s)
|
|
|
|
return s
|
|
|
|
|
|
|
|
def send_test_session_setup_request(self, fsts_id,
|
|
|
|
additional_parameter = None):
|
|
|
|
request = "FST-MANAGER TEST_REQUEST SEND_SETUP_REQUEST " + fsts_id
|
|
|
|
if additional_parameter is not None:
|
|
|
|
request += " " + additional_parameter
|
|
|
|
s = self.grequest(request)
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise Exception("Cannot send FST setup request: %s" % s)
|
|
|
|
return s
|
|
|
|
|
|
|
|
def send_test_session_setup_response(self, fsts_id,
|
|
|
|
response, additional_parameter = None):
|
|
|
|
request = "FST-MANAGER TEST_REQUEST SEND_SETUP_RESPONSE " + fsts_id + " " + response
|
|
|
|
if additional_parameter is not None:
|
|
|
|
request += " " + additional_parameter
|
|
|
|
s = self.grequest(request)
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise Exception("Cannot send FST setup response: %s" % s)
|
|
|
|
return s
|
|
|
|
|
|
|
|
def send_test_ack_request(self, fsts_id):
|
|
|
|
s = self.grequest("FST-MANAGER TEST_REQUEST SEND_ACK_REQUEST " + fsts_id)
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise Exception("Cannot send FST ack request: %s" % s)
|
|
|
|
return s
|
|
|
|
|
|
|
|
def send_test_ack_response(self, fsts_id):
|
|
|
|
s = self.grequest("FST-MANAGER TEST_REQUEST SEND_ACK_RESPONSE " + fsts_id)
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise Exception("Cannot send FST ack response: %s" % s)
|
|
|
|
return s
|
|
|
|
|
|
|
|
def send_test_tear_down(self, fsts_id):
|
|
|
|
s = self.grequest("FST-MANAGER TEST_REQUEST SEND_TEAR_DOWN " + fsts_id)
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise Exception("Cannot send FST tear down: %s" % s)
|
|
|
|
return s
|
|
|
|
|
|
|
|
def get_fsts_id_by_sid(self, sid):
|
|
|
|
s = self.grequest("FST-MANAGER TEST_REQUEST GET_FSTS_ID " + sid)
|
|
|
|
if s == ' ' or s.startswith('FAIL'):
|
|
|
|
raise Exception("Cannot get fsts_id for sid == " % sid)
|
|
|
|
return int(s)
|
|
|
|
|
|
|
|
def wait_for_iface_event(self, timeout):
|
|
|
|
while True:
|
|
|
|
ev = self.wait_gevent(["FST-EVENT-IFACE"], timeout)
|
|
|
|
if ev is None:
|
|
|
|
raise Exception("No FST-EVENT-IFACE received")
|
|
|
|
event = parse_fst_iface_event(ev)
|
|
|
|
if event is None:
|
|
|
|
# We can't parse so it's not our event, wait for next one
|
|
|
|
continue
|
|
|
|
return event
|
|
|
|
|
|
|
|
def wait_for_session_event(self, timeout, events_to_ignore=[],
|
|
|
|
events_to_count=[]):
|
|
|
|
while True:
|
|
|
|
ev = self.wait_gevent(["FST-EVENT-SESSION"], timeout)
|
|
|
|
if ev is None:
|
|
|
|
raise Exception("No FST-EVENT-SESSION received")
|
|
|
|
event = parse_fst_session_event(ev)
|
|
|
|
if event is None:
|
|
|
|
# We can't parse so it's not our event, wait for next one
|
|
|
|
continue
|
|
|
|
if len(events_to_ignore) > 0:
|
|
|
|
if event['type'] in events_to_ignore:
|
|
|
|
continue
|
|
|
|
elif len(events_to_count) > 0:
|
|
|
|
if not event['type'] in events_to_count:
|
|
|
|
continue
|
|
|
|
return event
|
|
|
|
|
|
|
|
def initiate_session(self, sid, response="accept"):
|
|
|
|
"""Initiates FST session with given session id 'sid'.
|
|
|
|
'response' is the session respond answer: "accept", "reject", or a
|
|
|
|
special "timeout" value to skip the response in order to test session
|
|
|
|
timeouts.
|
|
|
|
Returns: "OK" - session has been initiated, otherwise the reason for the
|
|
|
|
reset: REASON_REJECT, REASON_STT."""
|
|
|
|
strsid = ' ' + sid if sid != '' else ''
|
|
|
|
s = self.grequest("FST-MANAGER SESSION_INITIATE"+ strsid)
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise Exception("Cannot initiate fst session: %s" % s)
|
|
|
|
ev = self.peer_obj.wait_gevent([ "FST-EVENT-SESSION" ], timeout=5)
|
|
|
|
if ev is None:
|
|
|
|
raise Exception("No FST-EVENT-SESSION received")
|
|
|
|
# We got FST event
|
|
|
|
event = parse_fst_session_event(ev)
|
|
|
|
if event == None:
|
|
|
|
raise Exception("Unrecognized FST event: " % ev)
|
|
|
|
if event['type'] != 'EVENT_FST_SETUP':
|
|
|
|
raise Exception("Expected FST_SETUP event, got: " + event['type'])
|
|
|
|
ev = self.peer_obj.wait_gevent(["FST-EVENT-SESSION"], timeout=5)
|
|
|
|
if ev is None:
|
|
|
|
raise Exception("No FST-EVENT-SESSION received")
|
|
|
|
event = parse_fst_session_event(ev)
|
|
|
|
if event == None:
|
|
|
|
raise Exception("Unrecognized FST event: " % ev)
|
|
|
|
if event['type'] != 'EVENT_FST_SESSION_STATE':
|
|
|
|
raise Exception("Expected EVENT_FST_SESSION_STATE event, got: " + event['type'])
|
|
|
|
if event['new_state'] != "SETUP_COMPLETION":
|
|
|
|
raise Exception("Expected new state SETUP_COMPLETION, got: " + event['new_state'])
|
|
|
|
if response == '':
|
|
|
|
return 'OK'
|
|
|
|
if response != "timeout":
|
|
|
|
s = self.peer_obj.grequest("FST-MANAGER SESSION_RESPOND "+ event['id'] + " " + response) # Or reject
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise Exception("Error session_respond: %s" % s)
|
|
|
|
# Wait for EVENT_FST_SESSION_STATE events. We should get at least 2
|
|
|
|
# events. The 1st event will be EVENT_FST_SESSION_STATE
|
|
|
|
# old_state=INITIAL new_state=SETUP_COMPLETED. The 2nd event will be
|
|
|
|
# either EVENT_FST_ESTABLISHED with the session id or
|
|
|
|
# EVENT_FST_SESSION_STATE with new_state=INITIAL if the session was
|
|
|
|
# reset, the reason field will tell why.
|
|
|
|
result = ''
|
|
|
|
while result == '':
|
|
|
|
ev = self.wait_gevent(["FST-EVENT-SESSION"], timeout=5)
|
|
|
|
if ev is None:
|
|
|
|
break # No session event received
|
|
|
|
event = parse_fst_session_event(ev)
|
|
|
|
if event == None:
|
|
|
|
# We can't parse so it's not our event, wait for next one
|
|
|
|
continue
|
|
|
|
if event['type'] == 'EVENT_FST_ESTABLISHED':
|
|
|
|
result = "OK"
|
|
|
|
break
|
|
|
|
elif event['type'] == "EVENT_FST_SESSION_STATE":
|
|
|
|
if event['new_state'] == "INITIAL":
|
|
|
|
# Session was reset, the only reason to get back to initial
|
|
|
|
# state.
|
|
|
|
result = event['reason']
|
|
|
|
break
|
|
|
|
if result == '':
|
|
|
|
raise Exception("No event for session respond")
|
|
|
|
return result
|
|
|
|
|
|
|
|
def transfer_session(self, sid):
|
|
|
|
"""Transfers the session. 'sid' is the session id. 'hsta' is the
|
|
|
|
station-responder object.
|
|
|
|
Returns: REASON_SWITCH - the session has been transferred successfully
|
|
|
|
or a REASON_... reported by the reset event."""
|
|
|
|
request = "FST-MANAGER SESSION_TRANSFER"
|
|
|
|
if sid != '':
|
|
|
|
request += ' ' + sid
|
|
|
|
s = self.grequest(request)
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise Exception("Cannot transfer fst session: %s" % s)
|
|
|
|
result = ''
|
|
|
|
while result == '':
|
|
|
|
ev = self.peer_obj.wait_gevent([ "FST-EVENT-SESSION" ], timeout=5)
|
|
|
|
if ev is None:
|
|
|
|
raise Exception("Missing session transfer event")
|
|
|
|
# We got FST event. We expect TRANSITION_CONFIRMED state and then
|
|
|
|
# INITIAL (reset) with the reason (e.g. "REASON_SWITCH").
|
|
|
|
# Right now we'll be waiting for the reset event and record the
|
|
|
|
# reason.
|
|
|
|
event = parse_fst_session_event(ev)
|
|
|
|
if event == None:
|
|
|
|
raise Exception("Unrecognized FST event: " % ev)
|
|
|
|
if event['new_state'] == 'INITIAL':
|
|
|
|
result = event['reason']
|
|
|
|
return result
|
|
|
|
|
|
|
|
def wait_for_tear_down(self):
|
|
|
|
ev = self.wait_gevent([ "FST-EVENT-SESSION" ], timeout=5)
|
|
|
|
if ev is None:
|
|
|
|
raise Exception("No FST-EVENT-SESSION received")
|
|
|
|
# We got FST event
|
|
|
|
event = parse_fst_session_event(ev)
|
|
|
|
if event == None:
|
|
|
|
raise Exception("Unrecognized FST event: " % ev)
|
|
|
|
if event['type'] != 'EVENT_FST_SESSION_STATE':
|
|
|
|
raise Exception("Expected EVENT_FST_SESSION_STATE event, got: " + event['type'])
|
|
|
|
if event['new_state'] != "INITIAL":
|
|
|
|
raise Exception("Expected new state INITIAL, got: " + event['new_state'])
|
|
|
|
if event['reason'] != 'REASON_TEARDOWN':
|
|
|
|
raise Exception("Expected reason REASON_TEARDOWN, got: " + event['reason'])
|
|
|
|
|
|
|
|
def teardown_session(self, sid):
|
|
|
|
"""Tears down FST session with a given session id ('sid')"""
|
|
|
|
strsid = ' ' + sid if sid != '' else ''
|
|
|
|
s = self.grequest("FST-MANAGER SESSION_TEARDOWN" + strsid)
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise Exception("Cannot tear down fst session: %s" % s)
|
|
|
|
self.peer_obj.wait_for_tear_down()
|
|
|
|
|
|
|
|
|
|
|
|
def remove_session(self, sid, wait_for_tear_down=True):
|
|
|
|
"""Removes FST session with a given session id ('sid')"""
|
|
|
|
strsid = ' ' + sid if sid != '' else ''
|
|
|
|
s = self.grequest("FST-MANAGER SESSION_REMOVE" + strsid)
|
|
|
|
if not s.startswith('OK'):
|
|
|
|
raise Exception("Cannot remove fst session: %s" % s)
|
|
|
|
if wait_for_tear_down == True:
|
|
|
|
self.peer_obj.wait_for_tear_down()
|
|
|
|
|
|
|
|
def remove_all_sessions(self):
|
|
|
|
"""Removes FST session with a given session id ('sid')"""
|
|
|
|
grp = ' ' + self.fst_group if self.fst_group != '' else ''
|
|
|
|
s = self.grequest("FST-MANAGER LIST_SESSIONS" + grp)
|
|
|
|
if not s.startswith('FAIL'):
|
|
|
|
for sid in s.splitlines():
|
|
|
|
sid = sid.strip()
|
|
|
|
if len(sid) != 0:
|
|
|
|
self.remove_session(sid, wait_for_tear_down=False)
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# FstAP class
|
|
|
|
#
|
|
|
|
class FstAP (FstDevice):
|
|
|
|
def __init__(self, iface, ssid, mode, chan, fst_group, fst_pri,
|
2015-06-09 20:31:09 +02:00
|
|
|
fst_llt=None, rsn=False):
|
2014-08-05 17:25:59 +02:00
|
|
|
"""If fst_group is empty, then FST parameters will not be set
|
|
|
|
If fst_llt is empty, the parameter will not be set and the default value
|
|
|
|
is expected to be configured."""
|
|
|
|
self.ssid = ssid
|
|
|
|
self.mode = mode
|
|
|
|
self.chan = chan
|
|
|
|
self.reg_ctrl = fst_test_common.HapdRegCtrl()
|
|
|
|
self.reg_ctrl.add_ap(iface, self.chan)
|
|
|
|
self.global_instance = hostapd.HostapdGlobal()
|
2015-06-09 20:31:09 +02:00
|
|
|
FstDevice.__init__(self, iface, fst_group, fst_pri, fst_llt, rsn)
|
2014-08-05 17:25:59 +02:00
|
|
|
|
|
|
|
def start(self):
|
|
|
|
"""Starts AP the "standard" way as it was intended by hostapd tests.
|
|
|
|
This will work only when FST supports fully dynamically loading
|
|
|
|
parameters in hostapd."""
|
|
|
|
params = {}
|
|
|
|
params['ssid'] = self.ssid
|
|
|
|
params['hw_mode'] = self.mode
|
|
|
|
params['channel'] = self.chan
|
|
|
|
params['country_code'] = 'US'
|
2015-06-09 20:31:09 +02:00
|
|
|
if self.rsn:
|
|
|
|
params['wpa'] = '2'
|
|
|
|
params['wpa_key_mgmt'] = 'WPA-PSK'
|
|
|
|
params['rsn_pairwise'] = 'CCMP'
|
|
|
|
params['wpa_passphrase'] = '12345678'
|
2014-08-05 17:25:59 +02:00
|
|
|
self.hapd=hostapd.add_ap(self.iface, params)
|
|
|
|
if not self.hapd.ping():
|
|
|
|
raise Exception("Could not ping FST hostapd")
|
|
|
|
self.reg_ctrl.start()
|
|
|
|
self.get_global_instance()
|
|
|
|
if len(self.fst_group) != 0:
|
|
|
|
self.send_iface_attach_request(self.iface, self.fst_group,
|
|
|
|
self.fst_llt, self.fst_pri)
|
|
|
|
return self.hapd
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
"""Removes the AP, To be used when dynamic fst APs are implemented in
|
|
|
|
hostapd."""
|
|
|
|
if len(self.fst_group) != 0:
|
|
|
|
self.remove_all_sessions()
|
|
|
|
self.send_iface_detach_request(self.iface)
|
|
|
|
self.reg_ctrl.stop()
|
|
|
|
del self.global_instance
|
|
|
|
self.global_instance = None
|
|
|
|
|
|
|
|
def get_instance(self):
|
|
|
|
"""Return the Hostapd/WpaSupplicant instance"""
|
|
|
|
if self.instance is None:
|
|
|
|
self.instance = hostapd.Hostapd(self.iface)
|
|
|
|
return self.instance
|
|
|
|
|
|
|
|
def get_global_instance(self):
|
|
|
|
return self.global_instance
|
|
|
|
|
|
|
|
def get_own_mac_address(self):
|
|
|
|
"""Gets the device's own MAC address"""
|
|
|
|
h = self.get_instance()
|
|
|
|
status = h.get_status()
|
|
|
|
return status['bssid[0]']
|
|
|
|
|
|
|
|
def get_actual_peer_addr(self):
|
|
|
|
"""Gets the peer address. A connected station address is returned."""
|
|
|
|
# Use the device instance, the global control interface doesn't have
|
|
|
|
# station address
|
|
|
|
h = self.get_instance()
|
|
|
|
sta = h.get_sta(None)
|
|
|
|
if sta is None or 'addr' not in sta:
|
|
|
|
# Maybe station is not connected?
|
|
|
|
addr = None
|
|
|
|
else:
|
|
|
|
addr=sta['addr']
|
|
|
|
return addr
|
|
|
|
|
|
|
|
def grequest(self, req):
|
|
|
|
"""Send request on the global control interface"""
|
|
|
|
logger.debug("FstAP::grequest: " + req)
|
|
|
|
h = self.get_global_instance()
|
|
|
|
return h.request(req)
|
|
|
|
|
|
|
|
def wait_gevent(self, events, timeout=None):
|
|
|
|
"""Wait for a list of events on the global interface"""
|
|
|
|
h = self.get_global_instance()
|
|
|
|
if timeout is not None:
|
|
|
|
return h.wait_event(events, timeout=timeout)
|
|
|
|
else:
|
|
|
|
return h.wait_event(events)
|
|
|
|
|
|
|
|
def get_ssid(self):
|
|
|
|
return self.ssid
|
|
|
|
|
|
|
|
#
|
|
|
|
# FstSTA class
|
|
|
|
#
|
|
|
|
class FstSTA (FstDevice):
|
2015-06-09 20:31:09 +02:00
|
|
|
def __init__(self, iface, fst_group, fst_pri, fst_llt=None, rsn=False):
|
2014-08-05 17:25:59 +02:00
|
|
|
"""If fst_group is empty, then FST parameters will not be set
|
|
|
|
If fst_llt is empty, the parameter will not be set and the default value
|
|
|
|
is expected to be configured."""
|
2015-06-09 20:31:09 +02:00
|
|
|
FstDevice.__init__(self, iface, fst_group, fst_pri, fst_llt, rsn)
|
2014-08-05 17:25:59 +02:00
|
|
|
self.connected = None # FstAP object the station is connected to
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
"""Current implementation involves running another instance of
|
|
|
|
wpa_supplicant with fixed FST STAs configurations. When any type of
|
|
|
|
dynamic STA loading is implemented, rewrite the function similarly to
|
|
|
|
FstAP."""
|
|
|
|
h = self.get_instance()
|
|
|
|
h.interface_add(self.iface, drv_params="force_connect_cmd=1")
|
|
|
|
if not h.global_ping():
|
|
|
|
raise Exception("Could not ping FST wpa_supplicant")
|
|
|
|
if len(self.fst_group) != 0:
|
|
|
|
self.send_iface_attach_request(self.iface, self.fst_group,
|
|
|
|
self.fst_llt, self.fst_pri)
|
|
|
|
return None
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
"""Removes the STA. In a static (temporary) implementation does nothing,
|
|
|
|
the STA will be removed when the fst wpa_supplicant process is killed by
|
|
|
|
fstap.cleanup()."""
|
|
|
|
h = self.get_instance()
|
|
|
|
if len(self.fst_group) != 0:
|
|
|
|
self.remove_all_sessions()
|
|
|
|
self.send_iface_detach_request(self.iface)
|
|
|
|
h.interface_remove(self.iface)
|
|
|
|
h.close_ctrl()
|
|
|
|
del h
|
|
|
|
self.instance = None
|
|
|
|
|
|
|
|
def get_instance(self):
|
|
|
|
"""Return the Hostapd/WpaSupplicant instance"""
|
|
|
|
if self.instance is None:
|
|
|
|
self.instance = WpaSupplicant(global_iface='/tmp/wpas-wlan5')
|
|
|
|
return self.instance
|
|
|
|
|
|
|
|
def get_own_mac_address(self):
|
|
|
|
"""Gets the device's own MAC address"""
|
|
|
|
h = self.get_instance()
|
|
|
|
status = h.get_status()
|
|
|
|
return status['address']
|
|
|
|
|
|
|
|
def get_actual_peer_addr(self):
|
|
|
|
"""Gets the peer address. A connected station address is returned"""
|
|
|
|
h = self.get_instance()
|
|
|
|
status = h.get_status()
|
|
|
|
return status['bssid']
|
|
|
|
|
|
|
|
def grequest(self, req):
|
|
|
|
"""Send request on the global control interface"""
|
|
|
|
logger.debug("FstSTA::grequest: " + req)
|
|
|
|
h = self.get_instance()
|
|
|
|
return h.global_request(req)
|
|
|
|
|
|
|
|
def wait_gevent(self, events, timeout=None):
|
|
|
|
"""Wait for a list of events on the global interface"""
|
|
|
|
h = self.get_instance()
|
|
|
|
if timeout is not None:
|
|
|
|
return h.wait_global_event(events, timeout=timeout)
|
|
|
|
else:
|
|
|
|
return h.wait_global_event(events)
|
|
|
|
|
|
|
|
def scan(self, freq=None, no_wait=False, only_new=False):
|
|
|
|
"""Issue Scan with given parameters. Returns the BSS dictionary for the
|
|
|
|
AP found (the 1st BSS found. TODO: What if the AP required is not the
|
|
|
|
1st in list?) or None if no BSS found. None call be also a result of
|
|
|
|
no_wait=True. Note, request("SCAN_RESULTS") can be used to get all the
|
|
|
|
results at once."""
|
|
|
|
h = self.get_instance()
|
|
|
|
h.scan(None, freq, no_wait, only_new)
|
|
|
|
r = h.get_bss('0')
|
|
|
|
return r
|
|
|
|
|
|
|
|
def connect(self, ap, **kwargs):
|
|
|
|
"""Connects to the given AP"""
|
|
|
|
if not isinstance(ap, FstAP):
|
|
|
|
raise Exception("Bad AP object to connect to")
|
|
|
|
h = self.get_instance()
|
|
|
|
hap = ap.get_instance()
|
|
|
|
h.connect(ap.get_ssid(), **kwargs)
|
|
|
|
self.connected = ap
|
|
|
|
|
|
|
|
def connect_to_external_ap(self, ap, ssid, check_connection=True, **kwargs):
|
|
|
|
"""Connects to the given external AP"""
|
|
|
|
if not isinstance(ap, hostapd.Hostapd):
|
|
|
|
raise Exception("Bad AP object to connect to")
|
|
|
|
h = self.get_instance()
|
|
|
|
h.connect(ssid, **kwargs)
|
|
|
|
self.connected = ap
|
|
|
|
if check_connection:
|
|
|
|
ev = ap.wait_event([ "AP-STA-CONNECTED" ], timeout=10)
|
|
|
|
if ev is None:
|
|
|
|
self.connected = None
|
|
|
|
raise Exception("No connection event received from %s" % ssid)
|
|
|
|
|
|
|
|
def disconnect(self, check_disconnect=True):
|
|
|
|
"""Disconnects from the AP the station is currently connected to"""
|
|
|
|
if self.connected is not None:
|
|
|
|
h = self.get_instance()
|
|
|
|
h.request("DISCONNECT")
|
|
|
|
if check_disconnect:
|
|
|
|
hap = self.connected.get_instance()
|
|
|
|
ev = hap.wait_event([ "AP-STA-DISCONNECTED" ], timeout=10)
|
|
|
|
if ev is None:
|
|
|
|
raise Exception("No disconnection event received from %s" % self.connected.get_ssid())
|
|
|
|
self.connected = None
|
|
|
|
|
|
|
|
|
|
|
|
def disconnect_from_external_ap(self, check_disconnect=True):
|
|
|
|
"""Disconnects from the external AP the station is currently connected
|
|
|
|
to"""
|
|
|
|
if self.connected is not None:
|
|
|
|
h = self.get_instance()
|
|
|
|
h.request("DISCONNECT")
|
|
|
|
if check_disconnect:
|
|
|
|
hap = self.connected
|
|
|
|
ev = hap.wait_event([ "AP-STA-DISCONNECTED" ], timeout=10)
|
|
|
|
if ev is None:
|
|
|
|
raise Exception("No disconnection event received from AP")
|
|
|
|
self.connected = None
|