1 | # -*- coding: utf-8 -*- |
---|
2 | """ |
---|
3 | ------------------------------------------------------------------------------ |
---|
4 | Mango 802.11 Reference Design Experiments Framework - Access Point Node |
---|
5 | ------------------------------------------------------------------------------ |
---|
6 | License: Copyright 2019 Mango Communications, Inc. All rights reserved. |
---|
7 | Use and distribution subject to terms in LICENSE.txt |
---|
8 | ------------------------------------------------------------------------------ |
---|
9 | |
---|
10 | """ |
---|
11 | |
---|
12 | import wlan_exp.node as node |
---|
13 | import wlan_exp.cmds as cmds |
---|
14 | |
---|
15 | |
---|
16 | __all__ = ['WlanExpNodeAp'] |
---|
17 | |
---|
18 | |
---|
19 | class WlanExpNodeAp(node.WlanExpNode): |
---|
20 | """wlan_exp Node class for the 802.11 Reference Design AP MAC project |
---|
21 | |
---|
22 | Args: |
---|
23 | network_config (transport.NetworkConfiguration) : Network configuration of the node |
---|
24 | """ |
---|
25 | |
---|
26 | #------------------------------------------------------------------------- |
---|
27 | # Node Commands |
---|
28 | #------------------------------------------------------------------------- |
---|
29 | def configure_bss(self, bssid=False, ssid=None, channel=None, beacon_interval=False, dtim_period=None, ht_capable=None): |
---|
30 | """Configure the BSS information of the node |
---|
31 | |
---|
32 | Each node is either a member of no BSS (colloquially "unassociated") |
---|
33 | or a member of one BSS. A node requires a minimum valid set of BSS |
---|
34 | information to be a member of a BSS. The minimum valid set of BSS |
---|
35 | information for an AP is: |
---|
36 | |
---|
37 | #. BSSID: 48-bit MAC address |
---|
38 | #. Channel: Logical channel for Tx/Rx by BSS members |
---|
39 | #. SSID: Variable length string (ie the name of the network) |
---|
40 | #. Beacon Interval: Interval (in TUs) for beacons |
---|
41 | |
---|
42 | If a node is not a member of a BSS (i.e. ``n.get_network_info()`` returns |
---|
43 | ``None``), then the node requires all parameters of a minimum valid |
---|
44 | set of BSS information be specified (i.e. Channel, SSID, and |
---|
45 | Beacon Interval). For an AP, if BSSID is not specified, then it is |
---|
46 | assumed to be the wlan_mac_address of the node. |
---|
47 | |
---|
48 | See https://warpproject.org/trac/wiki/802.11/wlan_exp/bss |
---|
49 | for more documentation on BSS information / configuration. |
---|
50 | |
---|
51 | Args: |
---|
52 | bssid (int, str): 48-bit ID of the BSS either None or |
---|
53 | the wlan_mac_address of the node. If not specified, it is |
---|
54 | by default the wlan_mac_address of the node. |
---|
55 | ssid (str): SSID string (Must be 32 characters or less) |
---|
56 | channel (int): Channel number on which the BSS operates |
---|
57 | beacon_interval (int): Integer number of beacon Time Units in [10, 65534] |
---|
58 | (http://en.wikipedia.org/wiki/TU_(Time_Unit); a TU is 1024 microseconds); |
---|
59 | A value of None will disable beacons; A value of False will not |
---|
60 | update the current beacon interval. |
---|
61 | dtim_period (int): Integer number of beacon intervals between DTIMs |
---|
62 | ht_capable (bool): Is the PHY mode HTMF (True) or NONHT (False)? |
---|
63 | |
---|
64 | """ |
---|
65 | if (self.is_scanning() is True) and (bssid is None): |
---|
66 | print('Warning: network scan is still running when BSS was set to None. Use stop_active_scan if needed.') |
---|
67 | |
---|
68 | if bssid is not None: |
---|
69 | # Remember the bssid argument value, used in error checking the response below |
---|
70 | self.bssid = bssid |
---|
71 | if bssid is not False: |
---|
72 | # User supplied a not-None BSSID argument |
---|
73 | error = False |
---|
74 | |
---|
75 | if type(bssid) in [int, long]: |
---|
76 | if (bssid != self.wlan_mac_address): |
---|
77 | error = True |
---|
78 | elif type(bssid) is str: |
---|
79 | import wlan_exp.util as util |
---|
80 | try: |
---|
81 | if (util.str_to_mac_addr(bssid) != self.wlan_mac_address): |
---|
82 | error = True |
---|
83 | except: |
---|
84 | error = True |
---|
85 | |
---|
86 | if (error): |
---|
87 | raise AttributeError("BSSID must be either None or the wlan_mac_address of the node.") |
---|
88 | else: |
---|
89 | # User did not provide a BSSID argument - use the AP's MAC address |
---|
90 | bssid = self.wlan_mac_address |
---|
91 | resp_args = self.send_cmd(cmds.NodeConfigBSS(bssid=bssid, ssid=ssid, channel=channel, |
---|
92 | beacon_interval=beacon_interval, dtim_period=dtim_period, ht_capable=ht_capable)) |
---|
93 | |
---|
94 | # Process response arguments |
---|
95 | if (resp_args is not False): |
---|
96 | status = resp_args[0] |
---|
97 | msg = "ERROR:\n" |
---|
98 | ret_val = True |
---|
99 | |
---|
100 | # Check status |
---|
101 | if (status & cmds.ERROR_CONFIG_BSS_BSSID_INVALID): |
---|
102 | if type(self.bssid) in [int, long]: |
---|
103 | import wlan_exp.util as util |
---|
104 | self.bssid = util.mac_addr_to_str(self.bssid) |
---|
105 | msg += " BSSID {0} was invalid.\n".format(self.bssid) |
---|
106 | ret_val = False |
---|
107 | |
---|
108 | if (status & cmds.ERROR_CONFIG_BSS_BSSID_INSUFFICIENT_ARGUMENTS): |
---|
109 | msg += " Insufficient arguments to create BSS. Must provide:\n" |
---|
110 | if (bssid is False): |
---|
111 | msg += " BSSID\n" |
---|
112 | if (ssid is None): |
---|
113 | msg += " SSID\n" |
---|
114 | if (channel is None): |
---|
115 | msg += " CHANNEL\n" |
---|
116 | if (beacon_interval is False): |
---|
117 | msg += " BEACON_INTERVAL\n" |
---|
118 | ret_val = False |
---|
119 | |
---|
120 | if (status & cmds.ERROR_CONFIG_BSS_CHANNEL_INVALID): |
---|
121 | msg += " Channel {0} was invalid.\n".format(channel) |
---|
122 | ret_val = False |
---|
123 | |
---|
124 | if (status & cmds.ERROR_CONFIG_BSS_BEACON_INTERVAL_INVALID): |
---|
125 | msg += " Beacon interval {0} was invalid.\n".format(beacon_interval) |
---|
126 | ret_val = False |
---|
127 | |
---|
128 | if (status & cmds.ERROR_CONFIG_BSS_HT_CAPABLE_INVALID): |
---|
129 | msg += " HT capable {0} was invalid.\n".format(ht_capable) |
---|
130 | ret_val = False |
---|
131 | |
---|
132 | if (status & cmds.ERROR_CONFIG_BSS_DTIM_PERIOD_INVALID): |
---|
133 | msg += " DTIM period {0} was invalid.\n".format(dtim_period) |
---|
134 | ret_val = False |
---|
135 | |
---|
136 | if not ret_val: |
---|
137 | print(msg) |
---|
138 | |
---|
139 | |
---|
140 | def enable_beacon_mac_time_update(self, enable): |
---|
141 | """Enable / Disable MAC time update from beacons |
---|
142 | |
---|
143 | Raises NotImplementedError(). Current AP implementation does not |
---|
144 | support updating MAC time from beacon receptions |
---|
145 | |
---|
146 | Args: |
---|
147 | enable (bool): True - enable MAC time updates from beacons |
---|
148 | False - disable MAC time updates from beacons |
---|
149 | |
---|
150 | """ |
---|
151 | raise NotImplementedError("Current AP implementation does not support updating MAC time from beacon receptions") |
---|
152 | |
---|
153 | |
---|
154 | def is_associated(self, device_list): |
---|
155 | """Are the devices in the device_list in the AP association table? |
---|
156 | |
---|
157 | Args: |
---|
158 | device_list (WlanDevice): List of WLAN device (or sub-class of |
---|
159 | WLAN device) |
---|
160 | |
---|
161 | Returns: |
---|
162 | associated (list of bool): List of booleans describing whether each given device is associated with the AP |
---|
163 | |
---|
164 | If the device_list is a single device, then only a boolean is returned. |
---|
165 | If the device_list is a list of devices, then a list of booleans will |
---|
166 | be returned in the same order as the devices in the list. |
---|
167 | """ |
---|
168 | ret_val = [] |
---|
169 | ret_list = True |
---|
170 | is_member = False |
---|
171 | |
---|
172 | if device_list is not None: |
---|
173 | # Convert device_list to a list if it is not already one; set flag to not return a list |
---|
174 | if type(device_list) is not list: |
---|
175 | device_list = [device_list] |
---|
176 | ret_list = False |
---|
177 | |
---|
178 | for device in device_list: |
---|
179 | # Check the station info to see if device is there |
---|
180 | |
---|
181 | bss_member_list = self.get_bss_members() |
---|
182 | |
---|
183 | for bss_member in bss_member_list: |
---|
184 | if bss_member['mac_addr'] == device.wlan_mac_address: |
---|
185 | is_member = True |
---|
186 | |
---|
187 | if is_member is True: |
---|
188 | ret_val.append(True) |
---|
189 | else: |
---|
190 | ret_val.append(False) |
---|
191 | else: |
---|
192 | ret_val = False |
---|
193 | |
---|
194 | # Need to return a single value and not a list |
---|
195 | if not ret_list: |
---|
196 | ret_val = ret_val[0] |
---|
197 | |
---|
198 | return ret_val |
---|
199 | |
---|
200 | |
---|
201 | |
---|
202 | #------------------------------------------------------------------------- |
---|
203 | # Internal Node methods |
---|
204 | #------------------------------------------------------------------------- |
---|
205 | def _check_allowed_rate(self, mcs, phy_mode, verbose=False): |
---|
206 | """Check that rate parameters are allowed |
---|
207 | |
---|
208 | Args: |
---|
209 | mcs (int): Modulation and coding scheme (MCS) index |
---|
210 | phy_mode (str, int): PHY mode (from util.phy_modes) |
---|
211 | |
---|
212 | Returns: |
---|
213 | valid (bool): Are all parameters valid? |
---|
214 | """ |
---|
215 | |
---|
216 | # TODO: implement AP-specific rate checking here |
---|
217 | # Allow all supported rates for now |
---|
218 | |
---|
219 | return self._check_supported_rate(mcs, phy_mode, verbose) |
---|
220 | |
---|
221 | |
---|
222 | |
---|
223 | #------------------------------------------------------------------------- |
---|
224 | # AP specific commands |
---|
225 | #------------------------------------------------------------------------- |
---|
226 | def disassociate(self, device_list): |
---|
227 | """De-authenticate specific devices and remove the devices from the AP's |
---|
228 | association tables. This method triggers transmission of a de-authenticaion |
---|
229 | packet to the targeted STA nodes. The STA nodes are then removed from the AP |
---|
230 | association table. |
---|
231 | |
---|
232 | Args: |
---|
233 | device_list (list of WlanExpNode / WlanDevice): List of 802.11 |
---|
234 | devices or single 802.11 device for which to disassociate |
---|
235 | """ |
---|
236 | try: |
---|
237 | for device in device_list: |
---|
238 | self.send_cmd(cmds.NodeDisassociate(device)) |
---|
239 | except TypeError: |
---|
240 | self.send_cmd(cmds.NodeDisassociate(device_list)) |
---|
241 | |
---|
242 | |
---|
243 | def disassociate_all(self): |
---|
244 | """De-authenticates all devices and removes all devices from the AP's |
---|
245 | association tables. This method triggers transmission of a de-authenticaion |
---|
246 | packet to every associated STA node. The STA nodes are then removed from the AP |
---|
247 | association table. |
---|
248 | |
---|
249 | """ |
---|
250 | self.send_cmd(cmds.NodeDisassociate()) |
---|
251 | |
---|
252 | |
---|
253 | def enable_DTIM_multicast_buffering(self, enable): |
---|
254 | """Enable / Disable DTIM buffering of multicast data |
---|
255 | |
---|
256 | The Delivery Traffic Indication Map (DTIM) keeps track of STA sleep |
---|
257 | states and will buffer traffic for the node based on those sleep |
---|
258 | states. When an AP is configured with enable_DTIM_multicast_buffering(False), |
---|
259 | it will include the multicast queue in the normal polling of queues, |
---|
260 | independent of any STA sleep states. |
---|
261 | |
---|
262 | Args: |
---|
263 | enable (bool): True - enable DTIM multicast buffering |
---|
264 | False - disable DTIM multicast buffering |
---|
265 | (Default value on Node: True) |
---|
266 | """ |
---|
267 | self.send_cmd(cmds.NodeAPConfigure(dtim_multicast_buffering=enable)) |
---|
268 | |
---|
269 | |
---|
270 | def set_authentication_address_filter(self, allow): |
---|
271 | """Command to set the authentication address filter on the node. |
---|
272 | |
---|
273 | This command will reset the current address filter and then set the |
---|
274 | address filter to the values in the allow list. The filter only affects |
---|
275 | over-the-air associations. Assocaitions created by wlan_exp will |
---|
276 | bypass any filters configured by this method. |
---|
277 | |
---|
278 | Clients will be allowed to associate if they pass any of the filters |
---|
279 | that are set. |
---|
280 | |
---|
281 | Args: |
---|
282 | allow (list of tuple): List of (address, mask) tuples that will be |
---|
283 | used to filter addresses on the node. A tuple can be substituted |
---|
284 | with a predefined string: "NONE", "ALL", or "MANGO-W3" |
---|
285 | |
---|
286 | For the mask, bits that are 0 are treated as "any" and bits that are 1 |
---|
287 | are treated as "must equal". For the address, locations of one bits |
---|
288 | in the mask must match the incoming addresses to pass the filter. |
---|
289 | |
---|
290 | Examples: |
---|
291 | |
---|
292 | * Only allow client with MAC address ``01:23:45:67:89:AB``: |
---|
293 | |
---|
294 | >>> n_ap.set_authentication_address_filter(allow=(0x0123456789AB, 0xFFFFFFFFFFFF)) |
---|
295 | |
---|
296 | * Only allow clients with MAC addresses starting with ``01:23:45``: |
---|
297 | |
---|
298 | >>> n_ap.set_authentication_address_filter(allow=(0x012345000000, 0xFFFFFF000000)) |
---|
299 | |
---|
300 | * Allow clients with MAC addresses starting with ``01:23:45`` or ``40:`` |
---|
301 | |
---|
302 | >>> n_ap.set_authentication_address_filter(allow=[(0x012345000000, 0xFFFFFF000000), (0x400000000000, 0xFF0000000000)]) |
---|
303 | * Use one of the pre-defined address filter configurations: |
---|
304 | |
---|
305 | >>> n_ap.set_authentication_address_filter(allow='NONE') # Same as allow=(0x000000000000, 0xFFFFFFFFFFFF) |
---|
306 | >>> n_ap.set_authentication_address_filter(allow='ALL') # Same as allow=(0x000000000000, 0x000000000000) |
---|
307 | >>> n_ap.set_authentication_address_filter(allow='MANGO-W3') # Same as allow=(0x40d855042000, 0xFFFFFFFFF000) |
---|
308 | |
---|
309 | """ |
---|
310 | filters = [] |
---|
311 | |
---|
312 | if (type(allow) is not list): |
---|
313 | allow = [allow] |
---|
314 | |
---|
315 | for value in allow: |
---|
316 | # Process pre-defined strings |
---|
317 | if type(value) is str: |
---|
318 | if (value == 'NONE'): |
---|
319 | filters.append((0x000000000000, 0xFFFFFFFFFFFF)) |
---|
320 | elif (value == 'ALL'): |
---|
321 | filters.append((0x000000000000, 0x000000000000)) |
---|
322 | elif (value == 'MANGO-W3'): |
---|
323 | filters.append((0x40d855042000, 0xFFFFFFFFF000)) |
---|
324 | else: |
---|
325 | msg = "\n String '{0}' not recognized.".format(value) |
---|
326 | msg += "\n Please use 'NONE', 'ALL', 'MANGO-W3' or a (address, mask) tuple" |
---|
327 | raise AttributeError(msg) |
---|
328 | |
---|
329 | elif type(value) is tuple: |
---|
330 | import wlan_exp.util as util |
---|
331 | |
---|
332 | # Process address |
---|
333 | if type(value[0]) in [int, long]: |
---|
334 | address = value[0] |
---|
335 | elif type(value[0]) is str: |
---|
336 | try: |
---|
337 | address = util.str_to_mac_addr(value[0]) |
---|
338 | except: |
---|
339 | raise AttributeError("Address {0} is not valid".format(value[0])) |
---|
340 | else: |
---|
341 | raise AttributeError("Address type {0} is not valid".format(type(value[0]))) |
---|
342 | |
---|
343 | # Process mask |
---|
344 | if type(value[1]) in [int, long]: |
---|
345 | mask = value[1] |
---|
346 | elif type(value[1]) is str: |
---|
347 | try: |
---|
348 | mask = util.str_to_mac_addr(value[1]) |
---|
349 | except: |
---|
350 | raise AttributeError("Mask {0} is not valid".format(value[1])) |
---|
351 | else: |
---|
352 | raise AttributeError("Mask type {0} is not valid".format(type(value[1]))) |
---|
353 | |
---|
354 | filters.append((address, mask)) |
---|
355 | |
---|
356 | else: |
---|
357 | msg = "\n Value {0} with type {1} not recognized.".format(value, type(value)) |
---|
358 | msg += "\n Please use 'NONE', 'ALL', 'MANGO-W3' or a (address, mask) tuple" |
---|
359 | raise AttributeError(msg) |
---|
360 | |
---|
361 | self.send_cmd(cmds.NodeAPSetAuthAddrFilter(filters)) |
---|
362 | |
---|
363 | |
---|
364 | def add_association(self, device_list, disable_timeout=True): |
---|
365 | """Adds each device in ``device_list`` to the list of associated stations at the AP. If a device |
---|
366 | is also an 802.11 Reference Design STA, the STA is also configured with the BSS of the AP. In this |
---|
367 | case the AP and STA attain the same association state as if they had associated via the standard |
---|
368 | wireless handshake. This method bypasses any any authentication address filtering at the AP. |
---|
369 | |
---|
370 | Args: |
---|
371 | device_list (list of WlanExpNode / WlanDevice): List of 802.11 devices |
---|
372 | or single 802.11 device to add to the AP's association table |
---|
373 | disable_timeout (bool, optional): Disables the AP's normal inactivity timeout for the new associations. |
---|
374 | The AP periodically checks for associated stations with no recent Tx/Rx activity and removes inactive |
---|
375 | nodes from its list of associated stations. Set this parameter to True to force to AP to keep the new |
---|
376 | associations created by this method, even if the stations are inactive. |
---|
377 | """ |
---|
378 | ret_val = [] |
---|
379 | ret_list = True |
---|
380 | |
---|
381 | # Convert entries to a list if it is not already one |
---|
382 | if type(device_list) is not list: |
---|
383 | device_list = [device_list] |
---|
384 | ret_list = False |
---|
385 | |
---|
386 | # Get the AP's current Network information |
---|
387 | network_info = self.get_network_info() |
---|
388 | |
---|
389 | if network_info is None: |
---|
390 | msg = "\n Cannot add association: AP network configuration is currently null." |
---|
391 | msg += "\n Configure the AP's network using configure_bss() before calling add_association()." |
---|
392 | raise Exception(msg) |
---|
393 | |
---|
394 | bssid = network_info['bssid'] |
---|
395 | channel = network_info['channel'] |
---|
396 | ssid = network_info['ssid'] |
---|
397 | beacon_interval = network_info['beacon_interval'] |
---|
398 | |
---|
399 | if (network_info['ht_capable'] == 1): |
---|
400 | ht_capable = True |
---|
401 | else: |
---|
402 | ht_capable = False |
---|
403 | |
---|
404 | |
---|
405 | if (beacon_interval == 0): |
---|
406 | beacon_interval = None |
---|
407 | |
---|
408 | for device in device_list: |
---|
409 | ret_val.append(self._add_association(device=device, bssid=bssid, |
---|
410 | channel=channel, ssid=ssid, |
---|
411 | beacon_interval=beacon_interval, |
---|
412 | ht_capable=ht_capable, |
---|
413 | disable_timeout=disable_timeout)) |
---|
414 | |
---|
415 | # Need to return a single value and not a list |
---|
416 | if not ret_list: |
---|
417 | ret_val = ret_val[0] |
---|
418 | |
---|
419 | return ret_val |
---|
420 | |
---|
421 | |
---|
422 | |
---|
423 | #------------------------------------------------------------------------- |
---|
424 | # Internal AP methods |
---|
425 | #------------------------------------------------------------------------- |
---|
426 | def _add_association(self, device, bssid, channel, ssid, beacon_interval, ht_capable, disable_timeout): |
---|
427 | """Internal command to add an association.""" |
---|
428 | ret_val = False |
---|
429 | |
---|
430 | import wlan_exp.node_ibss as node_ibss |
---|
431 | |
---|
432 | if isinstance(device, node_ibss.WlanExpNodeIBSS): |
---|
433 | print("WARNING: Could not add association for IBSS node '{0}'".format(device.description)) |
---|
434 | return ret_val |
---|
435 | |
---|
436 | aid = self.send_cmd(cmds.NodeAPAddAssociation(device, disable_timeout)) |
---|
437 | |
---|
438 | if (aid != cmds.CMD_PARAM_ERROR): |
---|
439 | import wlan_exp.node_sta as node_sta |
---|
440 | |
---|
441 | if isinstance(device, node_sta.WlanExpNodeSta): |
---|
442 | device.configure_bss(bssid=bssid, ssid=ssid, channel=channel, |
---|
443 | beacon_interval=beacon_interval, ht_capable=ht_capable) |
---|
444 | device.set_aid(aid=aid) |
---|
445 | ret_val = True |
---|
446 | else: |
---|
447 | msg = "\nWARNING: Device {0} is not a wlan_exp node \n".format(device.description) |
---|
448 | msg += " instance. The device has been added to the AP's list of associated stations.\n" |
---|
449 | msg += " However, wlan_exp cannot update association state of the device.\n" |
---|
450 | print(msg) |
---|
451 | ret_val = True |
---|
452 | |
---|
453 | return ret_val |
---|
454 | |
---|
455 | # End class |
---|