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