Esempio n. 1
0
def callback_dump(msg, results):
    """Here is where SSIDs and their data is decoded from the binary data sent by the kernel.

    This function is called once per SSID. Everything in `msg` pertains to just one SSID.

    Positional arguments:
    msg -- nl_msg class instance containing the data sent by the kernel.
    results -- dictionary to populate with parsed data.
    """
    bss = dict()  # To be filled by nla_parse_nested().

    # First we must parse incoming data into manageable chunks and check for errors.
    gnlh = genlmsghdr(nlmsg_data(nlmsg_hdr(msg)))
    tb = dict((i, None) for i in range(nl80211.NL80211_ATTR_MAX + 1))
    nla_parse(tb, nl80211.NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), None)
    if not tb[nl80211.NL80211_ATTR_BSS]:
        print('WARNING: BSS info missing for an access point.')
        return libnl.handlers.NL_SKIP
    if nla_parse_nested(bss, nl80211.NL80211_BSS_MAX, tb[nl80211.NL80211_ATTR_BSS], bss_policy):
        print('WARNING: Failed to parse nested attributes for an access point!')
        return libnl.handlers.NL_SKIP
    if not bss[nl80211.NL80211_BSS_BSSID]:
        print('WARNING: No BSSID detected for an access point!')
        return libnl.handlers.NL_SKIP
    if not bss[nl80211.NL80211_BSS_INFORMATION_ELEMENTS]:
        print('WARNING: No additional information available for an access point!')
        return libnl.handlers.NL_SKIP

    # Further parse and then store. Overwrite existing data for BSSID if scan is run multiple times.
    bss_parsed = parse_bss(bss)
    results[bss_parsed['bssid']] = bss_parsed
    return libnl.handlers.NL_SKIP
Esempio n. 2
0
    def _callback_dump(self, msg, results):
        # Here is where SSIDs and their data is decoded from the binary data
        # sent by the kernel. This function is called once per SSID. Everything
        # in `msg` pertains to just one SSID.
        #
        # Positional arguments:
        # msg -- nl_msg class instance containing the data sent by the kernel.
        # results -- dictionary to populate with parsed data.
        bss = dict()  # To be filled by nla_parse_nested().

        # First we must parse incoming data into manageable chunks and check for errors.
        gnlh = genlmsghdr(nlmsg_data(nlmsg_hdr(msg)))
        tb = dict((i, None) for i in range(nl80211.NL80211_ATTR_MAX + 1))
        nla_parse(tb, nl80211.NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
                  genlmsg_attrlen(gnlh, 0), None)
        if not tb[nl80211.NL80211_ATTR_BSS]:
            logger.warning('BSS info missing for an access point.')
            return libnl.handlers.NL_SKIP
        if nla_parse_nested(bss, nl80211.NL80211_BSS_MAX,
                            tb[nl80211.NL80211_ATTR_BSS], bss_policy):
            logger.warning(
                'Failed to parse nested attributes for an access point!')
            return libnl.handlers.NL_SKIP
        if not bss[nl80211.NL80211_BSS_BSSID]:
            logger.warning('No BSSID detected for an access point!')
            return libnl.handlers.NL_SKIP
        if not bss[nl80211.NL80211_BSS_INFORMATION_ELEMENTS]:
            logger.warning(
                'No additional information available for an access point!')
            return libnl.handlers.NL_SKIP

        # Further parse and then store. Overwrite existing data for BSSID if scan is run multiple times.
        bss_parsed = parse_bss(bss)
        results[bss_parsed['bssid']] = bss_parsed
        return libnl.handlers.NL_SKIP
Esempio n. 3
0
def dump_callback(msg, _):

    gnlh = genlmsghdr(nlmsg_data(nlmsg_hdr(msg)))
    tb = dict((i, None) for i in range(NCSI_ATTR_MAX + 1))
    nla_parse(tb, NCSI_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
              genlmsg_attrlen(gnlh, 0), None)

    print(tb)
    return NL_SKIP
Esempio n. 4
0
    def callback_trigger(self, msg, arg):
        gnlh = genlmsghdr(nlmsg_data(nlmsg_hdr(msg)))

        tb = dict((i, None) for i in range(10 + 1))
        nla_parse(tb, nl80211.NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
                  genlmsg_attrlen(gnlh, 0), None)
        if tb.get(NL_CMD_RECOVERY_MSG):
            self.recovery = True

        return libnl.handlers.NL_STOP
def callback(msg, has_printed):
    """Callback function called by libnl upon receiving messages from the kernel.

    Positional arguments:
    msg -- nl_msg class instance containing the data sent by the kernel.
    has_printed -- simple pseudo boolean (if list is empty) to keep track of when to print emtpy lines.

    Returns:
    An integer, value of NL_SKIP. It tells libnl to stop calling other callbacks for this message and proceed with
    processing the next kernel message.
    """
    table = AsciiTable([['Data Type', 'Data Value']])
    # First convert `msg` into something more manageable.
    gnlh = genlmsghdr(nlmsg_data(nlmsg_hdr(msg)))

    # Partially parse the raw binary data and place them in the `tb` dictionary.
    tb = dict(
        (i, None)
        for i in range(nl80211.NL80211_ATTR_MAX +
                       1))  # Need to populate dict with all possible keys.
    nla_parse(tb, nl80211.NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
              genlmsg_attrlen(gnlh, 0), None)

    # Now it's time to grab the juicy data!
    if tb[nl80211.NL80211_ATTR_IFNAME]:
        table.title = nla_get_string(
            tb[nl80211.NL80211_ATTR_IFNAME]).decode('ascii')
    else:
        table.title = 'Unnamed Interface'

    if tb[nl80211.NL80211_ATTR_WIPHY]:
        wiphy_num = nla_get_u32(tb[nl80211.NL80211_ATTR_WIPHY])
        wiphy = ('wiphy {0}'
                 if OPTIONS['<interface>'] else 'phy#{0}').format(wiphy_num)
        table.table_data.append(['NL80211_ATTR_WIPHY', wiphy])

    if tb[nl80211.NL80211_ATTR_MAC]:
        mac_address = ':'.join(
            format(x, '02x')
            for x in nla_data(tb[nl80211.NL80211_ATTR_MAC])[:6])
        table.table_data.append(['NL80211_ATTR_MAC', mac_address])

    if tb[nl80211.NL80211_ATTR_IFINDEX]:
        table.table_data.append([
            'NL80211_ATTR_IFINDEX',
            str(nla_get_u32(tb[nl80211.NL80211_ATTR_IFINDEX]))
        ])

    # Print all data.
    if has_printed:
        print()
    else:
        has_printed.append(True)
    print(table.table)
    return NL_SKIP
Esempio n. 6
0
def callback(msg, has_printed):
    """Callback function called by libnl upon receiving messages from the kernel.

    Positional arguments:
    msg -- nl_msg class instance containing the data sent by the kernel.
    has_printed -- simple pseudo boolean (if list is empty) to keep track of when to print emtpy lines.

    Returns:
    An integer, value of NL_SKIP. It tells libnl to stop calling other callbacks for this message and proceed with
    processing the next kernel message.
    """
    table = AsciiTable([['Data Type', 'Data Value']])
    # First convert `msg` into something more manageable.
    gnlh = genlmsghdr(nlmsg_data(nlmsg_hdr(msg)))

    # Partially parse the raw binary data and place them in the `tb` dictionary.
    tb = dict((i, None) for i in range(nl80211.NL80211_ATTR_MAX + 1))  # Need to populate dict with all possible keys.
    nla_parse(tb, nl80211.NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), None)

    # Now it's time to grab the juicy data!
    if tb[nl80211.NL80211_ATTR_IFNAME]:
        table.title = nla_get_string(tb[nl80211.NL80211_ATTR_IFNAME]).decode('ascii')
    else:
        table.title = 'Unnamed Interface'

    if tb[nl80211.NL80211_ATTR_WIPHY]:
        wiphy_num = nla_get_u32(tb[nl80211.NL80211_ATTR_WIPHY])
        wiphy = ('wiphy {0}' if OPTIONS['<interface>'] else 'phy#{0}').format(wiphy_num)
        table.table_data.append(['NL80211_ATTR_WIPHY', wiphy])

    if tb[nl80211.NL80211_ATTR_MAC]:
        mac_address = ':'.join(format(x, '02x') for x in nla_data(tb[nl80211.NL80211_ATTR_MAC])[:6])
        table.table_data.append(['NL80211_ATTR_MAC', mac_address])

    if tb[nl80211.NL80211_ATTR_IFINDEX]:
        table.table_data.append(['NL80211_ATTR_IFINDEX', str(nla_get_u32(tb[nl80211.NL80211_ATTR_IFINDEX]))])

    # Print all data.
    if has_printed:
        print()
    else:
        has_printed.append(True)
    print(table.table)
    return NL_SKIP
    def _iface_callback(self, msg, _):
        # Callback function called by libnl upon receiving messages from the
        # kernel.
        #
        # Positional arguments:
        # msg -- nl_msg class instance containing the data sent by the kernel.
        #
        # Returns:
        # An integer, value of NL_SKIP. It tells libnl to stop calling other
        # callbacks for this message and proceed with processing the next kernel
        # message.
        # First convert `msg` into something more manageable.
        gnlh = genlmsghdr(nlmsg_data(nlmsg_hdr(msg)))

        # Partially parse the raw binary data and place them in the `tb`
        # dictionary. Need to populate dict with all possible keys.
        tb = dict((i, None) for i in range(nl80211.NL80211_ATTR_MAX + 1))
        nla_parse(tb, nl80211.NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
                  genlmsg_attrlen(gnlh, 0), None)

        # Now it's time to grab the data, we start with the interface index as
        # universal identifier
        if tb[nl80211.NL80211_ATTR_IFINDEX]:
            if_index = nla_get_u32(tb[nl80211.NL80211_ATTR_IFINDEX])
        else:
            return NL_SKIP

        # Create new interface dict if this interface is not yet known
        if if_index in self.iface_data:
            iface_data = self.iface_data[if_index]
        else:
            iface_data = {}

        if tb[nl80211.NL80211_ATTR_IFNAME]:
            iface_data['name'] = nla_get_string(
                tb[nl80211.NL80211_ATTR_IFNAME]).decode('ascii')

        if tb[nl80211.NL80211_ATTR_IFTYPE]:
            iftype = nla_get_u32(tb[nl80211.NL80211_ATTR_IFTYPE])

            if iftype == nl80211.NL80211_IFTYPE_UNSPECIFIED:
                typestr = 'UNSPECIFIED'
            elif iftype == nl80211.NL80211_IFTYPE_ADHOC:
                typestr = 'ADHOC'
            elif iftype == nl80211.NL80211_IFTYPE_STATION:
                typestr = 'STATION'
            elif iftype == nl80211.NL80211_IFTYPE_AP:
                typestr = 'AP'
            elif iftype == nl80211.NL80211_IFTYPE_AP_VLAN:
                typestr = 'AP_VLAN'
            elif iftype == nl80211.NL80211_IFTYPE_WDS:
                typestr = 'WDS'
            elif iftype == nl80211.NL80211_IFTYPE_MONITOR:
                typestr = 'MONITOR'
            elif iftype == nl80211.NL80211_IFTYPE_MESH_POINT:
                typestr = 'MESH_POINT'
            elif iftype == nl80211.NL80211_IFTYPE_P2P_CLIENT:
                typestr = 'P2P_CLIENT'
            elif iftype == nl80211.NL80211_IFTYPE_P2P_GO:
                typestr = 'P2P_GO'
            elif iftype == nl80211.NL80211_IFTYPE_P2P_DEVICE:
                typestr = 'P2P_DEVICE'

            iface_data['type'] = typestr

        if tb[nl80211.NL80211_ATTR_WIPHY]:
            wiphy_num = nla_get_u32(tb[nl80211.NL80211_ATTR_WIPHY])
            iface_data['wiphy'] = 'phy#{0}'.format(wiphy_num)

        if tb[nl80211.NL80211_ATTR_MAC]:
            mac_raw = nla_data(tb[nl80211.NL80211_ATTR_MAC])[:6]
            mac_address = ':'.join(format(x, '02x') for x in mac_raw)
            iface_data['mac'] = mac_address
            if (gnlh.cmd == nl80211.NL80211_CMD_NEW_STATION
                    and if_index == self.if_idx):
                # This is the BSSID that we're currently associated to
                self.bssid = mac_address

        if tb[nl80211.NL80211_ATTR_GENERATION]:
            generation = nla_get_u32(tb[nl80211.NL80211_ATTR_GENERATION])
            # Do not overwrite the generation for excessively large values
            if generation < 100:
                iface_data['generation'] = generation

        if tb[nl80211.NL80211_ATTR_WIPHY_TX_POWER_LEVEL]:
            iface_data['tx_power'] = nla_get_u32(
                tb[nl80211.NL80211_ATTR_WIPHY_TX_POWER_LEVEL]) / 100  # mW

        if tb[nl80211.NL80211_ATTR_CHANNEL_WIDTH]:
            iface_data['ch_width'] = nla_get_u32(
                tb[nl80211.NL80211_ATTR_CHANNEL_WIDTH])

        if tb[nl80211.NL80211_ATTR_CENTER_FREQ1]:
            iface_data['frequency'] = nla_get_u32(
                tb[nl80211.NL80211_ATTR_CENTER_FREQ1])

        # Station infos
        if tb[nl80211.NL80211_ATTR_STA_INFO]:
            # Need to unpack the data
            sinfo = dict(
                (i, None) for i in range(nl80211.NL80211_STA_INFO_MAX))
            rinfo = dict(
                (i, None) for i in range(nl80211.NL80211_STA_INFO_TX_BITRATE))

            # Extract data
            nla_parse_nested(sinfo, nl80211.NL80211_STA_INFO_MAX,
                             tb[nl80211.NL80211_ATTR_STA_INFO], None)

            # Extract info about signal strength (= quality)
            if sinfo[nl80211.NL80211_STA_INFO_SIGNAL]:
                iface_data['signal'] = 100 + \
                    nla_get_u8(sinfo[nl80211.NL80211_STA_INFO_SIGNAL])
                # Compute quality (formula found in iwinfo_nl80211.c and largely
                # simplified)
                iface_data['quality'] = iface_data['signal'] + 110
                iface_data['quality_max'] = 70

            # Extract info about negotiated bitrate
            if sinfo[nl80211.NL80211_STA_INFO_TX_BITRATE]:
                nla_parse_nested(rinfo, nl80211.NL80211_RATE_INFO_MAX,
                                 sinfo[nl80211.NL80211_STA_INFO_TX_BITRATE],
                                 None)
                if rinfo[nl80211.NL80211_RATE_INFO_BITRATE]:
                    iface_data['bitrate'] = nla_get_u16(
                        rinfo[nl80211.NL80211_RATE_INFO_BITRATE]) / 10

        # BSS info
        if tb[nl80211.NL80211_ATTR_BSS]:
            # Need to unpack the data
            binfo = dict((i, None) for i in range(nl80211.NL80211_BSS_MAX))
            nla_parse_nested(binfo, nl80211.NL80211_BSS_MAX,
                             tb[nl80211.NL80211_ATTR_BSS], None)

            # Parse BSS section (if complete)
            try:
                bss = parse_bss(binfo)

                # Remove duplicated information blocks
                if 'beacon_ies' in bss:
                    del bss['beacon_ies']
                if 'information_elements' in bss:
                    del bss['information_elements']
                if 'supported_rates' in bss:
                    del bss['supported_rates']

                # Convert timedelta objects for later JSON encoding
                for prop in bss:
                    if isinstance(bss[prop], datetime.timedelta):
                        bss[prop] = int(bss[prop].microseconds) / 1000

                # Append BSS data to general object
                iface_data = {**iface_data, **bss}
            except Exception as e:
                logger.warning("Obtaining BSS data failed: {}".format(e))
                pass

        # Append data to global structure
        self.iface_data[if_index] = iface_data
        return NL_SKIP
Esempio n. 8
0
def test_no_security():
    """iw output to test against.

    BSS 00:0d:67:23:b8:46(on wlan0)
    TSF: 1680943821184 usec (19d, 10:55:43)
    freq: 2412
    beacon interval: 100 TUs
    capability: ESS ShortPreamble ShortSlotTime (0x0421)
    signal: -66.00 dBm
    last seen: 4630 ms ago
    Information elements from Probe Response frame:
    SSID: CableWiFi
    Supported rates: 2.0* 5.5* 11.0* 6.0 9.0 12.0 18.0 24.0
    DS Parameter set: channel 1
    TIM: DTIM Count 0 DTIM Period 1 Bitmap Control 0x0 Bitmap[0] 0x0
    Country: US    Environment: Outdoor only
        Channels [1 - 11] @ 36 dBm
    ERP: <no flags>
    Extended supported rates: 36.0 48.0 54.0
    HT capabilities:
        Capabilities: 0x2d
            RX LDPC
            HT20
            SM Power Save disabled
            RX HT20 SGI
            No RX STBC
            Max AMSDU length: 3839 bytes
            No DSSS/CCK HT40
        Maximum RX AMPDU length 65535 bytes (exponent: 0x003)
        Minimum RX AMPDU time spacing: No restriction (0x00)
        HT RX MCS rate indexes supported: 0-23
        HT TX MCS rate indexes are undefined
    HT operation:
         * primary channel: 1
         * secondary channel offset: no secondary
         * STA channel width: 20 MHz
         * RIFS: 0
         * HT protection: nonmember
         * non-GF present: 0
         * OBSS non-GF present: 0
         * dual beacon: 0
         * dual CTS protection: 0
         * STBC beacon: 0
         * L-SIG TXOP Prot: 0
         * PCO active: 0
         * PCO phase: 0
    Overlapping BSS scan params:
         * passive dwell: 20 TUs
         * active dwell: 10 TUs
         * channel width trigger scan interval: 300 s
         * scan passive total per channel: 200 TUs
         * scan active total per channel: 20 TUs
         * BSS width channel transition delay factor: 5
         * OBSS Scan Activity Threshold: 0.25 %
    Extended capabilities: HT Information Exchange Supported, SSID List
    WMM:     * Parameter version 1
         * BE: CW 15-1023, AIFSN 3
         * BK: CW 15-1023, AIFSN 7
         * VI: CW 7-15, AIFSN 2, TXOP 3008 usec
         * VO: CW 3-7, AIFSN 2, TXOP 1504 usec
    """
    data = bytearray(base64.b64decode(
        b'IgEAAAgALgCJnxAACAADAAgAAAAMAJkAAQAAAAMAAACgAS8ACgABAAANZyO4RgAADAADAIAxD2CHAQAAowAGAAAJQ2FibGVXaUZpAQiEi5YME'
        b'hgkMAMBAQUEAAEAAAcGVVNPAQskKgEAMgNIYGwtGi0AA////wAAAAAAAAAAAAAAAAAABAbm5w0APRYBAAEAAAAAAAAAAAAAAAAAAAAAAAAASg'
        b'4UAAoALAHIABQABQAZAH8GAQAAAgAA3RgAUPICAQEHAAOkAAAnpAAAQkNeAGIyLwDdCQADfwEBAAD/fwAMAA0AgDEPYIcBAACjAAsAAAlDYWJ'
        b'sZVdpRmkBCISLlgwSGCQwAwEBBQQAAQAABwZVU08BCyQqAQAyA0hgbC0aLQAD////AAAAAAAAAAAAAAAAAAAEBubnDQA9FgEAAQAAAAAAAAAA'
        b'AAAAAAAAAAAAAABKDhQACgAsAcgAFAAFABkAfwYBAAACAADdGABQ8gIBAQcAA6QAACekAABCQ14AYjIvAN0JAAN/AQEAAP9/AAYABABkAAAAB'
        b'gAFACEEAAAIAAIAbAkAAAgADAAAAAAACAAKAGwgAAAIAAcAOOb//w=='
    ))
    gnlh = genlmsghdr(data)
    tb = dict((i, None) for i in range(NL80211_ATTR_MAX + 1))
    nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), None)
    bss = dict()
    nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy)
    bss_parsed = parse_bss(bss)
    assert '00:0d:67:23:b8:46' == bss_parsed['bssid']
    assert timedelta(microseconds=1680943821184) == bss_parsed['tsf']
    assert 2412 == bss_parsed['frequency']
    assert 100 == bss_parsed['beacon_interval']
    assert ['ESS', 'ShortPreamble', 'ShortSlotTime'] == sorted(bss_parsed['capability'])
    assert -66.0 == bss_parsed['signal']
    assert timedelta(milliseconds=4630) < bss_parsed['seen_ms_ago'] < timedelta(milliseconds=9999)
    assert u'CableWiFi' == bss_parsed['ssid']
    assert ['11.0*', '12.0', '18.0', '2.0*', '24.0', '5.5*', '6.0', '9.0'] == sorted(bss_parsed['supported_rates'])
    assert 1 == bss_parsed['channel']
    assert ['36.0', '48.0', '54.0'] == sorted(bss_parsed['extended_supported_rates'])
    assert 20 == bss_parsed['channel_width']
Esempio n. 9
0
def getStationInfo_callback(msg, results):
    # Dictionnary later populated with response message sub-attributes
    sinfo = dict()
    # Get the header of the message
    gnlh = genlmsghdr(nlmsg_data(nlmsg_hdr(msg)))
    tb = dict((i, None) for i in range(nl80211.NL80211_ATTR_MAX + 1))
    # Define the data structure of the netlink attributes we will receive
    stats_policy = dict(
        (i, None) for i in range(nl80211.NL80211_STA_INFO_MAX + 1))
    stats_policy.update({
        nl80211.NL80211_STA_INFO_INACTIVE_TIME:
        nla_policy(type_=NLA_U32),
        nl80211.NL80211_STA_INFO_RX_BYTES:
        nla_policy(type_=NLA_U32),
        nl80211.NL80211_STA_INFO_TX_BYTES:
        nla_policy(type_=NLA_U32),
        nl80211.NL80211_STA_INFO_RX_PACKETS:
        nla_policy(type_=NLA_U32),
        nl80211.NL80211_STA_INFO_TX_PACKETS:
        nla_policy(type_=NLA_U32),
        nl80211.NL80211_STA_INFO_SIGNAL:
        nla_policy(type_=NLA_U8),
        nl80211.NL80211_STA_INFO_SIGNAL_AVG:
        nla_policy(type_=NLA_U8),
        nl80211.NL80211_STA_INFO_T_OFFSET:
        nla_policy(type_=NLA_U64),
        nl80211.NL80211_STA_INFO_TX_BITRATE:
        nla_policy(type_=NLA_NESTED),
        nl80211.NL80211_STA_INFO_RX_BITRATE:
        nla_policy(type_=NLA_NESTED),
        nl80211.NL80211_STA_INFO_LLID:
        nla_policy(type_=NLA_U16),
        nl80211.NL80211_STA_INFO_PLID:
        nla_policy(type_=NLA_U16),
        nl80211.NL80211_STA_INFO_PLINK_STATE:
        nla_policy(type_=NLA_U8),
        nl80211.NL80211_STA_INFO_TX_RETRIES:
        nla_policy(type_=NLA_U32),
        nl80211.NL80211_STA_INFO_TX_FAILED:
        nla_policy(type_=NLA_U32),
        nl80211.NL80211_STA_INFO_LOCAL_PM:
        nla_policy(type_=NLA_U32),
        nl80211.NL80211_STA_INFO_PEER_PM:
        nla_policy(type_=NLA_U32),
        nl80211.NL80211_STA_INFO_NONPEER_PM:
        nla_policy(type_=NLA_U32),
        nl80211.NL80211_STA_INFO_CHAIN_SIGNAL:
        nla_policy(type_=NLA_NESTED),
        nl80211.NL80211_STA_INFO_RX_BYTES64:
        nla_policy(type_=NLA_U64),
        nl80211.NL80211_STA_INFO_TX_BYTES64:
        nla_policy(type_=NLA_U64),
        nl80211.NL80211_STA_INFO_BEACON_LOSS:
        nla_policy(type_=NLA_U32),
        nl80211.NL80211_STA_INFO_CONNECTED_TIME:
        nla_policy(type_=NLA_U32),
        nl80211.NL80211_STA_INFO_BSS_PARAM:
        nla_policy(type_=NLA_NESTED),
    })
    # If any value in the stats_policy is empty, pad it with a default NLA_U8 type to avoid
    # any issue during validation
    for key in stats_policy:
        if stats_policy[key] is None:
            stats_policy[key] = nla_policy(type_=NLA_U8)
    # Parse the stream of attributes received into indexed chunks of data
    nla_parse(tb, nl80211.NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
              genlmsg_attrlen(gnlh, 0), None)
    # If we haven't received Station info data, don't go further and skip this message
    if tb[nl80211.NL80211_ATTR_STA_INFO] is None:
        return libnl.handlers.NL_SKIP
    # Finally, feed the attributes of the message into the chunk defined before
    nla_parse_nested(sinfo, nl80211.NL80211_STA_INFO_MAX,
                     tb[nl80211.NL80211_ATTR_STA_INFO], stats_policy)
    # Create the Station object
    station = Station()
    # Finally, if an attribute of interest is present, save it in the object
    if tb[nl80211.NL80211_ATTR_MAC]:
        # Convert the station MAC address to something human readable
        raw_mac = nla_get_string(tb[nl80211.NL80211_ATTR_MAC])
        if len(raw_mac) == 6:
            station.mac_addr = "%x:%x:%x:%x:%x:%x" % struct.unpack(
                "BBBBBB", raw_mac)
    if sinfo[nl80211.NL80211_STA_INFO_RX_BYTES]:
        station.rx_bytes = nla_get_u32(
            sinfo[nl80211.NL80211_STA_INFO_RX_BYTES])
    if sinfo[nl80211.NL80211_STA_INFO_TX_BYTES]:
        station.tx_bytes = nla_get_u32(
            sinfo[nl80211.NL80211_STA_INFO_TX_BYTES])
    if sinfo[nl80211.NL80211_STA_INFO_RX_PACKETS]:
        station.rx_packets = nla_get_u32(
            sinfo[nl80211.NL80211_STA_INFO_RX_PACKETS])
    if sinfo[nl80211.NL80211_STA_INFO_TX_PACKETS]:
        station.tx_packets = nla_get_u32(
            sinfo[nl80211.NL80211_STA_INFO_TX_PACKETS])
    if sinfo[nl80211.NL80211_STA_INFO_TX_FAILED]:
        station.tx_failed = nla_get_u32(
            sinfo[nl80211.NL80211_STA_INFO_TX_FAILED])
    if sinfo[nl80211.NL80211_STA_INFO_SIGNAL]:
        # Signal level is saved as an 8-bit byte, so we convert it to a signed integer
        raw_signal = nla_get_u8(sinfo[nl80211.NL80211_STA_INFO_SIGNAL])
        if raw_signal > 127:
            station.signal = raw_signal - 256
        else:
            station.signal = raw_signal
    # Append the station to the list of station and iterate to the next result
    results.append(station)
    return libnl.handlers.NL_SKIP