1 # FST tests related classes
2 # Copyright (c) 2015, Qualcomm Atheros, Inc.
3 #
4 # This software may be distributed under the terms of the BSD license.
5 # See README for more details.
6 
7 import logging
8 import os
9 import signal
10 import time
11 import re
12 
13 import hostapd
14 import wpaspy
15 import utils
16 from wpasupplicant import WpaSupplicant
17 
18 import fst_test_common
19 
20 logger = logging.getLogger()
21 
22 def parse_fst_iface_event(ev):
23     """Parses FST iface event that comes as a string, e.g.
24     "<3>FST-EVENT-IFACE attached ifname=wlan9 group=fstg0"
25     Returns a dictionary with parsed "event_type", "ifname", and "group"; or
26     None if not an FST event or can't be parsed."""
27     event = {}
28     if ev.find("FST-EVENT-IFACE") == -1:
29         return None
30     if ev.find("attached") != -1:
31         event['event_type'] = 'attached'
32     elif ev.find("detached") != -1:
33         event['event_type'] = 'detached'
34     else:
35         return None
36     f = re.search(r"ifname=(\S+)", ev)
37     if f is not None:
38         event['ifname'] = f.group(1)
39     f = re.search(r"group=(\S+)", ev)
40     if f is not None:
41         event['group'] = f.group(1)
42     return event
43 
44 def parse_fst_session_event(ev):
45     """Parses FST session event that comes as a string, e.g.
46     "<3>FST-EVENT-SESSION event_type=EVENT_FST_SESSION_STATE session_id=0 reason=REASON_STT"
47     Returns a dictionary with parsed "type", "id", and "reason"; or None if not
48     a FST event or can't be parsed"""
49     event = {}
50     if ev.find("FST-EVENT-SESSION") == -1:
51         return None
52     event['new_state'] = '' # The field always exists in the dictionary
53     f = re.search(r"event_type=(\S+)", ev)
54     if f is None:
55         return None
56     event['type'] = f.group(1)
57     f = re.search(r"session_id=(\d+)", ev)
58     if f is not None:
59         event['id'] = f.group(1)
60     f = re.search(r"old_state=(\S+)", ev)
61     if f is not None:
62         event['old_state'] = f.group(1)
63     f = re.search(r"new_state=(\S+)", ev)
64     if f is not None:
65         event['new_state'] = f.group(1)
66     f = re.search(r"reason=(\S+)", ev)
67     if f is not None:
68         event['reason'] = f.group(1)
69     return event
70 
71 def start_two_ap_sta_pairs(apdev, rsn=False):
72     """auxiliary function that creates two pairs of APs and STAs"""
73     ap1 = FstAP(apdev[0]['ifname'], 'fst_11a', 'a',
74                 fst_test_common.fst_test_def_chan_a,
75                 fst_test_common.fst_test_def_group,
76                 fst_test_common.fst_test_def_prio_low,
77                 fst_test_common.fst_test_def_llt, rsn=rsn)
78     ap1.start()
79     ap2 = FstAP(apdev[1]['ifname'], 'fst_11g', 'g',
80                 fst_test_common.fst_test_def_chan_g,
81                 fst_test_common.fst_test_def_group,
82                 fst_test_common.fst_test_def_prio_high,
83                 fst_test_common.fst_test_def_llt, rsn=rsn)
84     ap2.start()
85 
86     sta1 = FstSTA('wlan5',
87                   fst_test_common.fst_test_def_group,
88                   fst_test_common.fst_test_def_prio_low,
89                   fst_test_common.fst_test_def_llt, rsn=rsn)
90     sta1.start()
91     sta2 = FstSTA('wlan6',
92                   fst_test_common.fst_test_def_group,
93                   fst_test_common.fst_test_def_prio_high,
94                   fst_test_common.fst_test_def_llt, rsn=rsn)
95     sta2.start()
96 
97     return ap1, ap2, sta1, sta2
98 
99 def stop_two_ap_sta_pairs(ap1, ap2, sta1, sta2):
100     sta1.stop()
101     sta2.stop()
102     ap1.stop()
103     ap2.stop()
104     fst_test_common.fst_clear_regdom()
105 
106 def connect_two_ap_sta_pairs(ap1, ap2, dev1, dev2, rsn=False):
107     """Connects a pair of stations, each one to a separate AP"""
108     dev1.scan(freq=fst_test_common.fst_test_def_freq_a)
109     dev2.scan(freq=fst_test_common.fst_test_def_freq_g)
110 
111     if rsn:
112         dev1.connect(ap1, psk="12345678",
113                      scan_freq=fst_test_common.fst_test_def_freq_a)
114         dev2.connect(ap2, psk="12345678",
115                      scan_freq=fst_test_common.fst_test_def_freq_g)
116     else:
117         dev1.connect(ap1, key_mgmt="NONE",
118                      scan_freq=fst_test_common.fst_test_def_freq_a)
119         dev2.connect(ap2, key_mgmt="NONE",
120                      scan_freq=fst_test_common.fst_test_def_freq_g)
121 
122 def disconnect_two_ap_sta_pairs(ap1, ap2, dev1, dev2):
123     dev1.disconnect()
124     dev2.disconnect()
125 
126 def external_sta_connect(sta, ap, **kwargs):
127     """Connects the external station to the given AP"""
128     if not isinstance(sta, WpaSupplicant):
129         raise Exception("Bad STA object")
130     if not isinstance(ap, FstAP):
131         raise Exception("Bad AP object to connect to")
132     hap = ap.get_instance()
133     sta.connect(ap.get_ssid(), **kwargs)
134 
135 def disconnect_external_sta(sta, ap, check_disconnect=True):
136     """Disconnects the external station from the AP"""
137     if not isinstance(sta, WpaSupplicant):
138         raise Exception("Bad STA object")
139     if not isinstance(ap, FstAP):
140         raise Exception("Bad AP object to connect to")
141     sta.request("DISCONNECT")
142     if check_disconnect:
143         hap = ap.get_instance()
144         ev = hap.wait_event(["AP-STA-DISCONNECTED"], timeout=10)
145         if ev is None:
146             raise Exception("No disconnection event received from %s" % ap.get_ssid())
147 
148 #
149 # FstDevice class
150 # This is the parent class for the AP (FstAP) and STA (FstSTA) that implements
151 # FST functionality.
152 #
153 class FstDevice:
154     def __init__(self, iface, fst_group, fst_pri, fst_llt=None, rsn=False):
155         self.iface = iface
156         self.fst_group = fst_group
157         self.fst_pri = fst_pri
158         self.fst_llt = fst_llt  # None llt means no llt parameter will be set
159         self.instance = None    # Hostapd/WpaSupplicant instance
160         self.peer_obj = None    # Peer object, must be a FstDevice child object
161         self.new_peer_addr = None # Peer MAC address for new session iface
162         self.old_peer_addr = None # Peer MAC address for old session iface
163         self.role = 'initiator' # Role: initiator/responder
164         s = self.grequest("FST-MANAGER TEST_REQUEST IS_SUPPORTED")
165         if not s.startswith('OK'):
166             raise utils.HwsimSkip("FST not supported")
167         self.rsn = rsn
168 
169     def ifname(self):
170         return self.iface
171 
172     def get_instance(self):
173         """Gets the Hostapd/WpaSupplicant instance"""
174         raise Exception("Virtual get_instance() called!")
175 
176     def get_own_mac_address(self):
177         """Gets the device's own MAC address"""
178         raise Exception("Virtual get_own_mac_address() called!")
179 
180     def get_new_peer_addr(self):
181         return self.new_peer_addr
182 
183     def get_old_peer_addr(self):
184         return self.old_peer_addr
185 
186     def get_actual_peer_addr(self):
187         """Gets the peer address. A connected AP/station address is returned."""
188         raise Exception("Virtual get_actual_peer_addr() called!")
189 
190     def grequest(self, req):
191         """Send request on the global control interface"""
192         raise Exception("Virtual grequest() called!")
193 
194     def wait_gevent(self, events, timeout=None):
195         """Wait for a list of events on the global interface"""
196         raise Exception("Virtual wait_gevent() called!")
197 
198     def request(self, req):
199         """Issue a request to the control interface"""
200         h = self.get_instance()
201         return h.request(req)
202 
203     def wait_event(self, events, timeout=None):
204         """Wait for an event from the control interface"""
205         h = self.get_instance()
206         if timeout is not None:
207             return h.wait_event(events, timeout=timeout)
208         else:
209             return h.wait_event(events)
210 
211     def set_old_peer_addr(self, peer_addr=None):
212         """Sets the peer address"""
213         if peer_addr is not None:
214             self.old_peer_addr = peer_addr
215         else:
216             self.old_peer_addr = self.get_actual_peer_addr()
217 
218     def set_new_peer_addr(self, peer_addr=None):
219         """Sets the peer address"""
220         if peer_addr is not None:
221             self.new_peer_addr = peer_addr
222         else:
223             self.new_peer_addr = self.get_actual_peer_addr()
224 
225     def add_peer(self, obj, old_peer_addr=None, new_peer_addr=None):
226         """Add peer for FST session(s). 'obj' is a FstDevice subclass object.
227         The method must be called before add_session().
228         If peer_addr is not specified, the address of the currently connected
229         station is used."""
230         if not isinstance(obj, FstDevice):
231             raise Exception("Peer must be a FstDevice object")
232         self.peer_obj = obj
233         self.set_old_peer_addr(old_peer_addr)
234         self.set_new_peer_addr(new_peer_addr)
235 
236     def get_peer(self):
237         """Returns peer object"""
238         return self.peer_obj
239 
240     def set_fst_parameters(self, group_id=None, pri=None, llt=None):
241         """Change/set new FST parameters. Can be used to start FST sessions with
242         different FST parameters than defined in the configuration file."""
243         if group_id is not None:
244             self.fst_group = group_id
245         if pri is not None:
246             self.fst_pri = pri
247         if llt is not None:
248             self.fst_llt = llt
249 
250     def get_local_mbies(self, ifname=None):
251         if_name = ifname if ifname is not None else self.iface
252         return self.grequest("FST-MANAGER TEST_REQUEST GET_LOCAL_MBIES " + if_name)
253 
254     def add_session(self):
255         """Adds an FST session. add_peer() must be called calling this
256         function"""
257         if self.peer_obj is None:
258             raise Exception("Peer wasn't added before starting session")
259         self.dump_monitor()
260         grp = ' ' + self.fst_group if self.fst_group != '' else ''
261         sid = self.grequest("FST-MANAGER SESSION_ADD" + grp)
262         sid = sid.strip()
263         if sid.startswith("FAIL"):
264             raise Exception("Cannot add FST session with groupid ==" + grp)
265         self.dump_monitor()
266         return sid
267 
268     def set_session_param(self, params):
269         request = "FST-MANAGER SESSION_SET"
270         if params is not None and params != '':
271             request = request + ' ' + params
272         return self.grequest(request)
273 
274     def get_session_params(self, sid):
275         request = "FST-MANAGER SESSION_GET " + sid
276         res = self.grequest(request)
277         if res.startswith("FAIL"):
278             return None
279         params = {}
280         for i in res.splitlines():
281             p = i.split('=')
282             params[p[0]] = p[1]
283         return params
284 
285     def iface_peers(self, ifname):
286         grp = self.fst_group if self.fst_group != '' else ''
287         res = self.grequest("FST-MANAGER IFACE_PEERS " + grp + ' ' + ifname)
288         if res.startswith("FAIL"):
289             return None
290         return res.splitlines()
291 
292     def get_peer_mbies(self, ifname, peer_addr):
293         return self.grequest("FST-MANAGER GET_PEER_MBIES %s %s" % (ifname, peer_addr))
294 
295     def list_ifaces(self):
296         grp = self.fst_group if self.fst_group != '' else ''
297         res = self.grequest("FST-MANAGER LIST_IFACES " + grp)
298         if res.startswith("FAIL"):
299             return None
300         ifaces = []
301         for i in res.splitlines():
302             p = i.split(':')
303             iface = {}
304             iface['name'] = p[0]
305             iface['priority'] = p[1]
306             iface['llt'] = p[2]
307             ifaces.append(iface)
308         return ifaces
309 
310     def list_groups(self):
311         res = self.grequest("FST-MANAGER LIST_GROUPS")
312         if res.startswith("FAIL"):
313             return None
314         return res.splitlines()
315 
316     def configure_session(self, sid, new_iface, old_iface=None):
317         """Calls session_set for a number of parameters some of which are stored
318         in "self" while others are passed to this function explicitly. If
319         old_iface is None, current iface is used; if old_iface is an empty
320         string."""
321         self.dump_monitor()
322         oldiface = old_iface if old_iface is not None else self.iface
323         s = self.set_session_param(sid + ' old_ifname=' + oldiface)
324         if not s.startswith("OK"):
325             raise Exception("Cannot set FST session old_ifname: " + s)
326         if new_iface is not None:
327             s = self.set_session_param(sid + " new_ifname=" + new_iface)
328             if not s.startswith("OK"):
329                 raise Exception("Cannot set FST session new_ifname:" + s)
330         if self.new_peer_addr is not None and self.new_peer_addr != '':
331             s = self.set_session_param(sid + " new_peer_addr=" + self.new_peer_addr)
332             if not s.startswith("OK"):
333                 raise Exception("Cannot set FST session peer address:" + s + " (new)")
334         if self.old_peer_addr is not None and self.old_peer_addr != '':
335             s = self.set_session_param(sid + " old_peer_addr=" + self.old_peer_addr)
336             if not s.startswith("OK"):
337                 raise Exception("Cannot set FST session peer address:" + s + " (old)")
338         if self.fst_llt is not None and self.fst_llt != '':
339             s = self.set_session_param(sid + " llt=" + self.fst_llt)
340             if not s.startswith("OK"):
341                 raise Exception("Cannot set FST session llt:" + s)
342         self.dump_monitor()
343 
344     def send_iface_attach_request(self, ifname, group, llt, priority):
345         request = "FST-ATTACH " + ifname + ' ' + group
346         if llt is not None:
347             request += " llt=" + llt
348         if priority is not None:
349             request += " priority=" + priority
350         res = self.grequest(request)
351         if not res.startswith("OK"):
352             raise Exception("Cannot attach FST iface: " + res)
353 
354     def send_iface_detach_request(self, ifname):
355         res = self.grequest("FST-DETACH " + ifname)
356         if not res.startswith("OK"):
357             raise Exception("Cannot detach FST iface: " + res)
358 
359     def send_session_setup_request(self, sid):
360         s = self.grequest("FST-MANAGER SESSION_INITIATE " + sid)
361         if not s.startswith('OK'):
362             raise Exception("Cannot send setup request: %s" % s)
363         return s
364 
365     def send_session_setup_response(self, sid, response):
366         request = "FST-MANAGER SESSION_RESPOND " + sid + " " + response
367         s = self.grequest(request)
368         if not s.startswith('OK'):
369             raise Exception("Cannot send setup response: %s" % s)
370         return s
371 
372     def send_test_session_setup_request(self, fsts_id,
373                                         additional_parameter=None):
374         request = "FST-MANAGER TEST_REQUEST SEND_SETUP_REQUEST " + fsts_id
375         if additional_parameter is not None:
376             request += " " + additional_parameter
377         s = self.grequest(request)
378         if not s.startswith('OK'):
379             raise Exception("Cannot send FST setup request: %s" % s)
380         return s
381 
382     def send_test_session_setup_response(self, fsts_id,
383                                          response, additional_parameter=None):
384         request = "FST-MANAGER TEST_REQUEST SEND_SETUP_RESPONSE " + fsts_id + " " + response
385         if additional_parameter is not None:
386             request += " " + additional_parameter
387         s = self.grequest(request)
388         if not s.startswith('OK'):
389             raise Exception("Cannot send FST setup response: %s" % s)
390         return s
391 
392     def send_test_ack_request(self, fsts_id):
393         s = self.grequest("FST-MANAGER TEST_REQUEST SEND_ACK_REQUEST " + fsts_id)
394         if not s.startswith('OK'):
395             raise Exception("Cannot send FST ack request: %s" % s)
396         return s
397 
398     def send_test_ack_response(self, fsts_id):
399         s = self.grequest("FST-MANAGER TEST_REQUEST SEND_ACK_RESPONSE " + fsts_id)
400         if not s.startswith('OK'):
401             raise Exception("Cannot send FST ack response: %s" % s)
402         return s
403 
404     def send_test_tear_down(self, fsts_id):
405         s = self.grequest("FST-MANAGER TEST_REQUEST SEND_TEAR_DOWN " + fsts_id)
406         if not s.startswith('OK'):
407             raise Exception("Cannot send FST tear down: %s" % s)
408         return s
409 
410     def get_fsts_id_by_sid(self, sid):
411         s = self.grequest("FST-MANAGER TEST_REQUEST GET_FSTS_ID " + sid)
412         if s == ' ' or s.startswith('FAIL'):
413             raise Exception("Cannot get fsts_id for sid == %s" % sid)
414         return int(s)
415 
416     def wait_for_iface_event(self, timeout):
417         while True:
418             ev = self.wait_gevent(["FST-EVENT-IFACE"], timeout)
419             if ev is None:
420                 raise Exception("No FST-EVENT-IFACE received")
421             event = parse_fst_iface_event(ev)
422             if event is None:
423                 # We can't parse so it's not our event, wait for next one
424                 continue
425             return event
426 
427     def wait_for_session_event(self, timeout, events_to_ignore=[],
428                                events_to_count=[]):
429         while True:
430             ev = self.wait_gevent(["FST-EVENT-SESSION"], timeout)
431             if ev is None:
432                 raise Exception("No FST-EVENT-SESSION received")
433             event = parse_fst_session_event(ev)
434             if event is None:
435                 # We can't parse so it's not our event, wait for next one
436                 continue
437             if len(events_to_ignore) > 0:
438                 if event['type'] in events_to_ignore:
439                     continue
440             elif len(events_to_count) > 0:
441                 if event['type'] not in events_to_count:
442                     continue
443             return event
444 
445     def initiate_session(self, sid, response="accept"):
446         """Initiates FST session with given session id 'sid'.
447         'response' is the session respond answer: "accept", "reject", or a
448         special "timeout" value to skip the response in order to test session
449         timeouts.
450         Returns: "OK" - session has been initiated, otherwise the reason for the
451         reset: REASON_REJECT, REASON_STT."""
452         strsid = ' ' + sid if sid != '' else ''
453         s = self.grequest("FST-MANAGER SESSION_INITIATE"+ strsid)
454         if not s.startswith('OK'):
455             raise Exception("Cannot initiate fst session: %s" % s)
456         ev = self.peer_obj.wait_gevent(["FST-EVENT-SESSION"], timeout=5)
457         if ev is None:
458             raise Exception("No FST-EVENT-SESSION received")
459         # We got FST event
460         event = parse_fst_session_event(ev)
461         if event == None:
462             raise Exception("Unrecognized FST event: " % ev)
463         if event['type'] != 'EVENT_FST_SETUP':
464             raise Exception("Expected FST_SETUP event, got: " + event['type'])
465         ev = self.peer_obj.wait_gevent(["FST-EVENT-SESSION"], timeout=5)
466         if ev is None:
467             raise Exception("No FST-EVENT-SESSION received")
468         event = parse_fst_session_event(ev)
469         if event == None:
470             raise Exception("Unrecognized FST event: " % ev)
471         if event['type'] != 'EVENT_FST_SESSION_STATE':
472             raise Exception("Expected EVENT_FST_SESSION_STATE event, got: " + event['type'])
473         if event['new_state'] != "SETUP_COMPLETION":
474             raise Exception("Expected new state SETUP_COMPLETION, got: " + event['new_state'])
475         if response == '':
476             return 'OK'
477         if response != "timeout":
478             s = self.peer_obj.grequest("FST-MANAGER SESSION_RESPOND "+ event['id'] + " " + response)  # Or reject
479             if not s.startswith('OK'):
480                 raise Exception("Error session_respond: %s" % s)
481         # Wait for EVENT_FST_SESSION_STATE events. We should get at least 2
482         # events. The 1st event will be EVENT_FST_SESSION_STATE
483         # old_state=INITIAL new_state=SETUP_COMPLETED. The 2nd event will be
484         # either EVENT_FST_ESTABLISHED with the session id or
485         # EVENT_FST_SESSION_STATE with new_state=INITIAL if the session was
486         # reset, the reason field will tell why.
487         result = ''
488         while result == '':
489             ev = self.wait_gevent(["FST-EVENT-SESSION"], timeout=5)
490             if ev is None:
491                 break # No session event received
492             event = parse_fst_session_event(ev)
493             if event == None:
494                 # We can't parse so it's not our event, wait for next one
495                 continue
496             if event['type'] == 'EVENT_FST_ESTABLISHED':
497                 result = "OK"
498                 break
499             elif event['type'] == "EVENT_FST_SESSION_STATE":
500                 if event['new_state'] == "INITIAL":
501                     # Session was reset, the only reason to get back to initial
502                     # state.
503                     result = event['reason']
504                     break
505         if result == '':
506             raise Exception("No event for session respond")
507         return result
508 
509     def transfer_session(self, sid):
510         """Transfers the session. 'sid' is the session id. 'hsta' is the
511         station-responder object.
512         Returns: REASON_SWITCH - the session has been transferred successfully
513         or a REASON_... reported by the reset event."""
514         request = "FST-MANAGER SESSION_TRANSFER"
515         self.dump_monitor()
516         if sid != '':
517             request += ' ' + sid
518         s = self.grequest(request)
519         if not s.startswith('OK'):
520             raise Exception("Cannot transfer fst session: %s" % s)
521         result = ''
522         while result == '':
523             ev = self.peer_obj.wait_gevent(["FST-EVENT-SESSION"], timeout=5)
524             if ev is None:
525                 raise Exception("Missing session transfer event")
526             # We got FST event. We expect TRANSITION_CONFIRMED state and then
527             # INITIAL (reset) with the reason (e.g. "REASON_SWITCH").
528             # Right now we'll be waiting for the reset event and record the
529             # reason.
530             event = parse_fst_session_event(ev)
531             if event == None:
532                 raise Exception("Unrecognized FST event: " % ev)
533             if event['new_state'] == 'INITIAL':
534                 result = event['reason']
535         self.dump_monitor()
536         return result
537 
538     def wait_for_tear_down(self):
539         ev = self.wait_gevent(["FST-EVENT-SESSION"], timeout=5)
540         if ev is None:
541             raise Exception("No FST-EVENT-SESSION received")
542         # We got FST event
543         event = parse_fst_session_event(ev)
544         if event == None:
545             raise Exception("Unrecognized FST event: " % ev)
546         if event['type'] != 'EVENT_FST_SESSION_STATE':
547             raise Exception("Expected EVENT_FST_SESSION_STATE event, got: " + event['type'])
548         if event['new_state'] != "INITIAL":
549             raise Exception("Expected new state INITIAL, got: " + event['new_state'])
550         if event['reason'] != 'REASON_TEARDOWN':
551             raise Exception("Expected reason REASON_TEARDOWN, got: " + event['reason'])
552 
553     def teardown_session(self, sid):
554         """Tears down FST session with a given session id ('sid')"""
555         strsid = ' ' + sid if sid != '' else ''
556         s = self.grequest("FST-MANAGER SESSION_TEARDOWN" + strsid)
557         if not s.startswith('OK'):
558             raise Exception("Cannot tear down fst session: %s" % s)
559         self.peer_obj.wait_for_tear_down()
560 
561 
562     def remove_session(self, sid, wait_for_tear_down=True):
563         """Removes FST session with a given session id ('sid')"""
564         strsid = ' ' + sid if sid != '' else ''
565         s = self.grequest("FST-MANAGER SESSION_REMOVE" + strsid)
566         if not s.startswith('OK'):
567             raise Exception("Cannot remove fst session: %s" % s)
568         if wait_for_tear_down == True:
569             self.peer_obj.wait_for_tear_down()
570 
571     def remove_all_sessions(self):
572         """Removes FST session with a given session id ('sid')"""
573         grp = ' ' + self.fst_group if self.fst_group != '' else ''
574         s = self.grequest("FST-MANAGER LIST_SESSIONS" + grp)
575         if not s.startswith('FAIL'):
576             for sid in s.splitlines():
577                 sid = sid.strip()
578                 if len(sid) != 0:
579                     self.remove_session(sid, wait_for_tear_down=False)
580 
581 
582 #
583 # FstAP class
584 #
585 class FstAP(FstDevice):
586     def __init__(self, iface, ssid, mode, chan, fst_group, fst_pri,
587                  fst_llt=None, rsn=False):
588         """If fst_group is empty, then FST parameters will not be set
589         If fst_llt is empty, the parameter will not be set and the default value
590         is expected to be configured."""
591         self.ssid = ssid
592         self.mode = mode
593         self.chan = chan
594         self.reg_ctrl = fst_test_common.HapdRegCtrl()
595         self.reg_ctrl.add_ap(iface, self.chan)
596         self.global_instance = hostapd.HostapdGlobal()
597         FstDevice.__init__(self, iface, fst_group, fst_pri, fst_llt, rsn)
598 
599     def start(self, return_early=False):
600         """Starts AP the "standard" way as it was intended by hostapd tests.
601         This will work only when FST supports fully dynamically loading
602         parameters in hostapd."""
603         params = {}
604         params['ssid'] = self.ssid
605         params['hw_mode'] = self.mode
606         params['channel'] = self.chan
607         params['country_code'] = 'US'
608         if self.rsn:
609             params['wpa'] = '2'
610             params['wpa_key_mgmt'] = 'WPA-PSK'
611             params['rsn_pairwise'] = 'CCMP'
612             params['wpa_passphrase'] = '12345678'
613         self.hapd = hostapd.add_ap(self.iface, params)
614         if not self.hapd.ping():
615             raise Exception("Could not ping FST hostapd")
616         self.reg_ctrl.start()
617         self.get_global_instance()
618         if return_early:
619             return self.hapd
620         if len(self.fst_group) != 0:
621             self.send_iface_attach_request(self.iface, self.fst_group,
622                                            self.fst_llt, self.fst_pri)
623         return self.hapd
624 
625     def stop(self):
626         """Removes the AP, To be used when dynamic fst APs are implemented in
627         hostapd."""
628         if len(self.fst_group) != 0:
629             self.remove_all_sessions()
630             try:
631                 self.send_iface_detach_request(self.iface)
632             except Exception as e:
633                 logger.info(str(e))
634         self.reg_ctrl.stop()
635         del self.global_instance
636         self.global_instance = None
637 
638     def get_instance(self):
639         """Return the Hostapd/WpaSupplicant instance"""
640         if self.instance is None:
641             self.instance = hostapd.Hostapd(self.iface)
642         return self.instance
643 
644     def get_global_instance(self):
645         return self.global_instance
646 
647     def get_own_mac_address(self):
648         """Gets the device's own MAC address"""
649         h = self.get_instance()
650         status = h.get_status()
651         return status['bssid[0]']
652 
653     def get_actual_peer_addr(self):
654         """Gets the peer address. A connected station address is returned."""
655         # Use the device instance, the global control interface doesn't have
656         # station address
657         h = self.get_instance()
658         sta = h.get_sta(None)
659         if sta is None or 'addr' not in sta:
660             # Maybe station is not connected?
661             addr = None
662         else:
663             addr = sta['addr']
664         return addr
665 
666     def grequest(self, req):
667         """Send request on the global control interface"""
668         logger.debug("FstAP::grequest: " + req)
669         h = self.get_global_instance()
670         return h.request(req)
671 
672     def wait_gevent(self, events, timeout=None):
673         """Wait for a list of events on the global interface"""
674         h = self.get_global_instance()
675         if timeout is not None:
676             return h.wait_event(events, timeout=timeout)
677         else:
678             return h.wait_event(events)
679 
680     def get_ssid(self):
681         return self.ssid
682 
683     def dump_monitor(self):
684         """Dump control interface monitor events"""
685         if self.instance:
686             self.instance.dump_monitor()
687 
688 #
689 # FstSTA class
690 #
691 class FstSTA(FstDevice):
692     def __init__(self, iface, fst_group, fst_pri, fst_llt=None, rsn=False):
693         """If fst_group is empty, then FST parameters will not be set
694         If fst_llt is empty, the parameter will not be set and the default value
695         is expected to be configured."""
696         FstDevice.__init__(self, iface, fst_group, fst_pri, fst_llt, rsn)
697         self.connected = None # FstAP object the station is connected to
698 
699     def start(self):
700         """Current implementation involves running another instance of
701         wpa_supplicant with fixed FST STAs configurations. When any type of
702         dynamic STA loading is implemented, rewrite the function similarly to
703         FstAP."""
704         h = self.get_instance()
705         h.interface_add(self.iface, drv_params="force_connect_cmd=1")
706         if not h.global_ping():
707             raise Exception("Could not ping FST wpa_supplicant")
708         if len(self.fst_group) != 0:
709             self.send_iface_attach_request(self.iface, self.fst_group,
710                                            self.fst_llt, self.fst_pri)
711         return None
712 
713     def stop(self):
714         """Removes the STA. In a static (temporary) implementation does nothing,
715         the STA will be removed when the fst wpa_supplicant process is killed by
716         fstap.cleanup()."""
717         h = self.get_instance()
718         h.dump_monitor()
719         if len(self.fst_group) != 0:
720             self.remove_all_sessions()
721             self.send_iface_detach_request(self.iface)
722             h.dump_monitor()
723         h.interface_remove(self.iface)
724         h.close_ctrl()
725         del h
726         self.instance = None
727 
728     def get_instance(self):
729         """Return the Hostapd/WpaSupplicant instance"""
730         if self.instance is None:
731              self.instance = WpaSupplicant(global_iface='/tmp/wpas-wlan5')
732         return self.instance
733 
734     def get_own_mac_address(self):
735         """Gets the device's own MAC address"""
736         h = self.get_instance()
737         status = h.get_status()
738         return status['address']
739 
740     def get_actual_peer_addr(self):
741         """Gets the peer address. A connected station address is returned"""
742         h = self.get_instance()
743         status = h.get_status()
744         return status['bssid']
745 
746     def grequest(self, req):
747         """Send request on the global control interface"""
748         logger.debug("FstSTA::grequest: " + req)
749         h = self.get_instance()
750         return h.global_request(req)
751 
752     def wait_gevent(self, events, timeout=None):
753         """Wait for a list of events on the global interface"""
754         h = self.get_instance()
755         if timeout is not None:
756             return h.wait_global_event(events, timeout=timeout)
757         else:
758             return h.wait_global_event(events)
759 
760     def scan(self, freq=None, no_wait=False, only_new=False):
761         """Issue Scan with given parameters. Returns the BSS dictionary for the
762         AP found (the 1st BSS found. TODO: What if the AP required is not the
763         1st in list?) or None if no BSS found. None call be also a result of
764         no_wait=True. Note, request("SCAN_RESULTS") can be used to get all the
765         results at once."""
766         h = self.get_instance()
767         h.dump_monitor()
768         h.scan(None, freq, no_wait, only_new)
769         r = h.get_bss('0')
770         h.dump_monitor()
771         return r
772 
773     def connect(self, ap, **kwargs):
774         """Connects to the given AP"""
775         if not isinstance(ap, FstAP):
776             raise Exception("Bad AP object to connect to")
777         h = self.get_instance()
778         hap = ap.get_instance()
779         h.dump_monitor()
780         h.connect(ap.get_ssid(), **kwargs)
781         h.dump_monitor()
782         self.connected = ap
783 
784     def connect_to_external_ap(self, ap, ssid, check_connection=True, **kwargs):
785         """Connects to the given external AP"""
786         if not isinstance(ap, hostapd.Hostapd):
787             raise Exception("Bad AP object to connect to")
788         h = self.get_instance()
789         h.dump_monitor()
790         h.connect(ssid, **kwargs)
791         self.connected = ap
792         if check_connection:
793             ev = ap.wait_event(["AP-STA-CONNECTED"], timeout=10)
794             if ev is None:
795                 self.connected = None
796                 raise Exception("No connection event received from %s" % ssid)
797             h.dump_monitor()
798 
799     def disconnect(self, check_disconnect=True):
800         """Disconnects from the AP the station is currently connected to"""
801         if self.connected is not None:
802             h = self.get_instance()
803             h.dump_monitor()
804             h.request("DISCONNECT")
805             if check_disconnect:
806                 hap = self.connected.get_instance()
807                 ev = hap.wait_event(["AP-STA-DISCONNECTED"], timeout=10)
808                 if ev is None:
809                     raise Exception("No disconnection event received from %s" % self.connected.get_ssid())
810                 h.dump_monitor()
811             self.connected = None
812 
813 
814     def disconnect_from_external_ap(self, check_disconnect=True):
815         """Disconnects from the external AP the station is currently connected
816         to"""
817         if self.connected is not None:
818             h = self.get_instance()
819             h.dump_monitor()
820             h.request("DISCONNECT")
821             if check_disconnect:
822                 hap = self.connected
823                 ev = hap.wait_event(["AP-STA-DISCONNECTED"], timeout=10)
824                 if ev is None:
825                     raise Exception("No disconnection event received from AP")
826                 h.dump_monitor()
827             self.connected = None
828 
829     def dump_monitor(self):
830         """Dump control interface monitor events"""
831         if self.instance:
832             self.instance.dump_monitor()
833