source: ReferenceDesigns/w3_802.11/python/wlan_exp/node_ap.py

Last change on this file was 6320, checked in by chunter, 5 years ago

1.8.0 release wlan-exp

File size: 19.7 KB
Line 
1# -*- coding: utf-8 -*-
2"""
3------------------------------------------------------------------------------
4Mango 802.11 Reference Design Experiments Framework - Access Point Node
5------------------------------------------------------------------------------
6License:   Copyright 2019 Mango Communications, Inc. All rights reserved.
7           Use and distribution subject to terms in LICENSE.txt
8------------------------------------------------------------------------------
9
10"""
11
12import wlan_exp.node as node
13import wlan_exp.cmds as cmds
14
15
16__all__ = ['WlanExpNodeAp']
17
18
19class 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
Note: See TracBrowser for help on using the repository browser.