def genlmsg_put(msg, port, seq, family, hdrlen, flags, cmd, version): """Add Generic Netlink headers to Netlink message. https://github.com/thom311/libnl/blob/libnl3_2_25/lib/genl/genl.c#L348 Calls nlmsg_put() on the specified message object to reserve space for the Netlink header, the Generic Netlink header, and a user header of specified length. Fills out the header fields with the specified parameters. Positional arguments: msg -- Netlink message object (nl_msg class instance). port -- Netlink port or NL_AUTO_PORT (c_uint32). seq -- sequence number of message or NL_AUTO_SEQ (c_uint32). family -- numeric family identifier (integer). hdrlen -- length of user header (integer). flags -- additional Netlink message flags (integer). cmd -- numeric command identifier (c_uint8). version -- interface version (c_uint8). Returns: bytearray starting at user header or None if an error occurred. """ hdr = genlmsghdr(cmd=cmd, version=version) nlh = nlmsg_put(msg, port, seq, family, GENL_HDRLEN + hdrlen, flags) if nlh is None: return None nlmsg_data(nlh)[:hdr.SIZEOF] = hdr.bytearray[:hdr.SIZEOF] _LOGGER.debug('msg 0x%x: Added generic netlink header cmd=%d version=%d', id(msg), cmd, version) return bytearray_ptr(nlmsg_data(nlh), GENL_HDRLEN)
def genlmsg_parse(nlh, hdrlen, tb, maxtype, policy): """Parse Generic Netlink message including attributes. https://github.com/thom311/libnl/blob/libnl3_2_25/lib/genl/genl.c#L191 Verifies the validity of the Netlink and Generic Netlink headers using genlmsg_valid_hdr() and calls nla_parse() on the message payload to parse eventual attributes. Positional arguments: nlh -- Netlink message header (nlmsghdr class instance). hdrlen -- length of user header (integer). tb -- empty dict, to be updated with nlattr class instances to store parsed attributes. maxtype -- maximum attribute id expected (integer). policy -- dictionary of nla_policy class instances as values, with nla types as keys. Returns: 0 on success or a negative error code. """ if not genlmsg_valid_hdr(nlh, hdrlen): return -NLE_MSG_TOOSHORT ghdr = genlmsghdr(nlmsg_data(nlh)) return int( nla_parse(tb, maxtype, genlmsg_attrdata(ghdr, hdrlen), genlmsg_attrlen(ghdr, hdrlen), policy))
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
def callback(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_OK. It tells libnl to proceed with processing the next kernel message. """ # First convert `msg` into something more manageable. nlh = nlmsg_hdr(msg) iface = ifinfomsg(nlmsg_data(nlh)) hdr = IFLA_RTA(iface) remaining = ctypes.c_int(nlh.nlmsg_len - NLMSG_LENGTH(iface.SIZEOF)) # Now iterate through each rtattr stored in `iface`. while RTA_OK(hdr, remaining): # Each rtattr (which is what hdr is) instance is only one type. Looping through all of them until we run into # the ones we care about. if hdr.rta_type == IFLA_IFNAME: print('Found network interface {0}: {1}'.format( iface.ifi_index, get_string(RTA_DATA(hdr)).decode('ascii'))) hdr = RTA_NEXT(hdr, remaining) return NL_OK
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
def genlmsg_valid_hdr(nlh, hdrlen): """Validate Generic Netlink message headers. https://github.com/thom311/libnl/blob/libnl3_2_25/lib/genl/genl.c#L117 Verifies the integrity of the Netlink and Generic Netlink headers by enforcing the following requirements: - Valid Netlink message header (`nlmsg_valid_hdr()`) - Presence of a complete Generic Netlink header - At least `hdrlen` bytes of payload included after the generic Netlink header. Positional arguments: nlh -- Netlink message header (nlmsghdr class instance). hdrlen -- length of user header (integer). Returns: True if the headers are valid or False if not. """ if not nlmsg_valid_hdr(nlh, GENL_HDRLEN): return False ghdr = genlmsghdr(nlmsg_data(nlh)) if genlmsg_len(ghdr) < NLMSG_ALIGN(hdrlen): return False return True
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
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
def callback(msg, arg): nlh = nlmsg_hdr(msg) iface = ifinfomsg(nlmsg_data(nlh)) hdr = IFLA_RTA(iface) remaining = c_int(nlh.nlmsg_len - NLMSG_LENGTH(iface.SIZEOF)) while RTA_OK(hdr, remaining): if hdr.rta_type == IFLA_IFNAME: arg[int(iface.ifi_index)] = str(get_string(RTA_DATA(hdr)).decode('ascii')) hdr = RTA_NEXT(hdr, remaining) return NL_OK
def genlmsg_hdr(nlh): """Return reference to Generic Netlink header. https://github.com/thom311/libnl/blob/libnl3_2_25/lib/genl/genl.c#L210 Positional arguments: nlh -- Netlink message header (nlmsghdr class instance). Returns: Reference to Generic Netlink message header. """ return nlmsg_data(nlh)
def callback_trigger(msg, arg): """Called when the kernel is done scanning. Only signals if it was successful or if it failed. No other data. Positional arguments: msg -- nl_msg class instance containing the data sent by the kernel. arg -- mutable integer (ctypes.c_int()) to update with results. 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. """ gnlh = genlmsghdr(nlmsg_data(nlmsg_hdr(msg))) if gnlh.cmd == nl80211.NL80211_CMD_SCAN_ABORTED: arg.value = 1 # The scan was aborted for some reason. elif gnlh.cmd == nl80211.NL80211_CMD_NEW_SCAN_RESULTS: arg.value = 0 # The scan completed successfully. `callback_dump` will collect the results later. return libnl.handlers.NL_SKIP
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 genlmsg_parse(nlh, hdrlen, tb, maxtype, policy): """Parse Generic Netlink message including attributes. https://github.com/thom311/libnl/blob/libnl3_2_25/lib/genl/genl.c#L191 Verifies the validity of the Netlink and Generic Netlink headers using genlmsg_valid_hdr() and calls nla_parse() on the message payload to parse eventual attributes. Positional arguments: nlh -- Netlink message header (nlmsghdr class instance). hdrlen -- length of user header (integer). tb -- empty dict, to be updated with nlattr class instances to store parsed attributes. maxtype -- maximum attribute id expected (integer). policy -- dictionary of nla_policy class instances as values, with nla types as keys. Returns: 0 on success or a negative error code. """ if not genlmsg_valid_hdr(nlh, hdrlen): return -NLE_MSG_TOOSHORT ghdr = genlmsghdr(nlmsg_data(nlh)) return int(nla_parse(tb, maxtype, genlmsg_attrdata(ghdr, hdrlen), genlmsg_attrlen(ghdr, hdrlen), policy))
def callback(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_OK. It tells libnl to proceed with processing the next kernel message. """ # First convert `msg` into something more manageable. nlh = nlmsg_hdr(msg) iface = ifinfomsg(nlmsg_data(nlh)) hdr = IFLA_RTA(iface) remaining = ctypes.c_int(nlh.nlmsg_len - NLMSG_LENGTH(iface.SIZEOF)) # Now iterate through each rtattr stored in `iface`. while RTA_OK(hdr, remaining): # Each rtattr (which is what hdr is) instance is only one type. Looping through all of them until we run into # the ones we care about. if hdr.rta_type == IFLA_IFNAME: print('Found network interface {0}: {1}'.format(iface.ifi_index, get_string(RTA_DATA(hdr)).decode('ascii'))) hdr = RTA_NEXT(hdr, remaining) return NL_OK
def recvmsgs(sk, cb): """https://github.com/thom311/libnl/blob/libnl3_2_25/lib/nl.c#L775. This is where callbacks are called. Positional arguments: sk -- Netlink socket (nl_sock class instance). cb -- callbacks (nl_cb class instance). Returns: Number of bytes received or a negative error code. """ multipart = 0 interrupted = 0 nrecv = 0 buf = bytearray() # nla is passed on to not only to nl_recv() but may also be passed to a function pointer provided by the caller # which may or may not initialize the variable. Thomas Graf. nla = sockaddr_nl() creds = ucred() while True: # This is the `goto continue_reading` implementation. _LOGGER.debug('Attempting to read from 0x%x', id(sk)) n = c_int( cb.cb_recv_ow(sk, nla, buf, creds) if cb. cb_recv_ow else nl_recv(sk, nla, buf, creds)) if n.value <= 0: return n.value _LOGGER.debug('recvmsgs(0x%x): Read %d bytes', id(sk), n.value) hdr = nlmsghdr(bytearray_ptr(buf)) while nlmsg_ok(hdr, n): _LOGGER.debug('recvmsgs(0x%x): Processing valid message...', id(sk)) msg = nlmsg_convert(hdr) nlmsg_set_proto(msg, sk.s_proto) nlmsg_set_src(msg, nla) if creds: raise NotImplementedError # nlmsg_set_creds(msg, creds) nrecv += 1 # Raw callback is the first, it gives the most control to the user and he can do his very own parsing. if cb.cb_set[NL_CB_MSG_IN]: err = nl_cb_call(cb, NL_CB_MSG_IN, msg) # NL_CB_CALL(cb, NL_CB_MSG_IN, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) if cb.cb_set[NL_CB_SEQ_CHECK]: # Sequence number checking. The check may be done by the user, otherwise a very simple check is applied # enforcing strict ordering. err = nl_cb_call(cb, NL_CB_SEQ_CHECK, msg) # NL_CB_CALL(cb, NL_CB_SEQ_CHECK, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) elif not sk.s_flags & NL_NO_AUTO_ACK: # Only do sequence checking if auto-ack mode is enabled. if hdr.nlmsg_seq != sk.s_seq_expect: if cb.cb_set[NL_CB_INVALID]: err = nl_cb_call( cb, NL_CB_INVALID, msg) # NL_CB_CALL(cb, NL_CB_INVALID, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else ( err or nrecv) else: return -NLE_SEQ_MISMATCH if hdr.nlmsg_type in (NLMSG_DONE, NLMSG_ERROR, NLMSG_NOOP, NLMSG_OVERRUN): # We can't check for !NLM_F_MULTI since some Netlink users in the kernel are broken. sk.s_seq_expect += 1 _LOGGER.debug( 'recvmsgs(0x%x): Increased expected sequence number to %d', id(sk), sk.s_seq_expect) if hdr.nlmsg_flags & NLM_F_MULTI: multipart = 1 if hdr.nlmsg_flags & NLM_F_DUMP_INTR: if cb.cb_set[NL_CB_DUMP_INTR]: err = nl_cb_call( cb, NL_CB_DUMP_INTR, msg) # NL_CB_CALL(cb, NL_CB_DUMP_INTR, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) else: # We have to continue reading to clear all messages until a NLMSG_DONE is received and report the # inconsistency. interrupted = 1 if hdr.nlmsg_flags & NLM_F_ACK: # Other side wishes to see an ack for this message. if cb.cb_set[NL_CB_SEND_ACK]: err = nl_cb_call( cb, NL_CB_SEND_ACK, msg) # NL_CB_CALL(cb, NL_CB_SEND_ACK, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) if hdr.nlmsg_type == NLMSG_DONE: # Messages terminates a multipart message, this is usually the end of a message and therefore we slip # out of the loop by default. the user may overrule this action by skipping this packet. multipart = 0 if cb.cb_set[NL_CB_FINISH]: err = nl_cb_call(cb, NL_CB_FINISH, msg) # NL_CB_CALL(cb, NL_CB_FINISH, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) elif hdr.nlmsg_type == NLMSG_NOOP: # Message to be ignored, the default action is to skip this message if no callback is specified. The # user may overrule this action by returning NL_PROCEED. if cb.cb_set[NL_CB_SKIPPED]: err = nl_cb_call(cb, NL_CB_SKIPPED, msg) # NL_CB_CALL(cb, NL_CB_SKIPPED, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) else: hdr = nlmsg_next(hdr, n) continue elif hdr.nlmsg_type == NLMSG_OVERRUN: # Data got lost, report back to user. The default action is to quit parsing. The user may overrule this # action by retuning NL_SKIP or NL_PROCEED (dangerous). if cb.cb_set[NL_CB_OVERRUN]: err = nl_cb_call(cb, NL_CB_OVERRUN, msg) # NL_CB_CALL(cb, NL_CB_OVERRUN, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) else: return -NLE_DUMP_INTR if interrupted else -NLE_MSG_OVERFLOW elif hdr.nlmsg_type == NLMSG_ERROR: # Message carries a nlmsgerr. e = nlmsgerr(nlmsg_data(hdr)) if hdr.nlmsg_len < nlmsg_size(e.SIZEOF): # Truncated error message, the default action is to stop parsing. The user may overrule this action # by returning NL_SKIP or NL_PROCEED (dangerous). if cb.cb_set[NL_CB_INVALID]: err = nl_cb_call( cb, NL_CB_INVALID, msg) # NL_CB_CALL(cb, NL_CB_INVALID, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else ( err or nrecv) else: return -NLE_DUMP_INTR if interrupted else -NLE_MSG_TRUNC elif e.error: # Error message reported back from kernel. if cb.cb_err: err = cb.cb_err(nla, e, cb.cb_err_arg) if err < 0: return -NLE_DUMP_INTR if interrupted else err elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else -nl_syserr2nlerr( e.error) else: return -NLE_DUMP_INTR if interrupted else -nl_syserr2nlerr( e.error) elif cb.cb_set[NL_CB_ACK]: err = nl_cb_call(cb, NL_CB_ACK, msg) # NL_CB_CALL(cb, NL_CB_ACK, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) else: # Valid message (not checking for MULTIPART bit to get along with broken kernels. NL_SKIP has no effect # on this. if cb.cb_set[NL_CB_VALID]: err = nl_cb_call(cb, NL_CB_VALID, msg) # NL_CB_CALL(cb, NL_CB_VALID, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) hdr = nlmsg_next(hdr, n) del buf[:] creds = None if multipart: # Multipart message not yet complete, continue reading. continue err = 0 if interrupted: return -NLE_DUMP_INTR if not err: err = nrecv return err
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
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
def recvmsgs(sk, cb): """https://github.com/thom311/libnl/blob/libnl3_2_25/lib/nl.c#L775. This is where callbacks are called. Positional arguments: sk -- Netlink socket (nl_sock class instance). cb -- callbacks (nl_cb class instance). Returns: Number of bytes received or a negative error code. """ multipart = 0 interrupted = 0 nrecv = 0 buf = bytearray() # nla is passed on to not only to nl_recv() but may also be passed to a function pointer provided by the caller # which may or may not initialize the variable. Thomas Graf. nla = sockaddr_nl() creds = ucred() while True: # This is the `goto continue_reading` implementation. _LOGGER.debug('Attempting to read from 0x%x', id(sk)) n = c_int(cb.cb_recv_ow(sk, nla, buf, creds) if cb.cb_recv_ow else nl_recv(sk, nla, buf, creds)) if n.value <= 0: return n.value _LOGGER.debug('recvmsgs(0x%x): Read %d bytes', id(sk), n.value) hdr = nlmsghdr(bytearray_ptr(buf)) while nlmsg_ok(hdr, n): _LOGGER.debug('recvmsgs(0x%x): Processing valid message...', id(sk)) msg = nlmsg_convert(hdr) nlmsg_set_proto(msg, sk.s_proto) nlmsg_set_src(msg, nla) if creds: raise NotImplementedError # nlmsg_set_creds(msg, creds) nrecv += 1 # Raw callback is the first, it gives the most control to the user and he can do his very own parsing. if cb.cb_set[NL_CB_MSG_IN]: err = nl_cb_call(cb, NL_CB_MSG_IN, msg) # NL_CB_CALL(cb, NL_CB_MSG_IN, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) if cb.cb_set[NL_CB_SEQ_CHECK]: # Sequence number checking. The check may be done by the user, otherwise a very simple check is applied # enforcing strict ordering. err = nl_cb_call(cb, NL_CB_SEQ_CHECK, msg) # NL_CB_CALL(cb, NL_CB_SEQ_CHECK, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) elif not sk.s_flags & NL_NO_AUTO_ACK: # Only do sequence checking if auto-ack mode is enabled. if hdr.nlmsg_seq != sk.s_seq_expect: if cb.cb_set[NL_CB_INVALID]: err = nl_cb_call(cb, NL_CB_INVALID, msg) # NL_CB_CALL(cb, NL_CB_INVALID, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) else: return -NLE_SEQ_MISMATCH if hdr.nlmsg_type in (NLMSG_DONE, NLMSG_ERROR, NLMSG_NOOP, NLMSG_OVERRUN): # We can't check for !NLM_F_MULTI since some Netlink users in the kernel are broken. sk.s_seq_expect += 1 _LOGGER.debug('recvmsgs(0x%x): Increased expected sequence number to %d', id(sk), sk.s_seq_expect) if hdr.nlmsg_flags & NLM_F_MULTI: multipart = 1 if hdr.nlmsg_flags & NLM_F_DUMP_INTR: if cb.cb_set[NL_CB_DUMP_INTR]: err = nl_cb_call(cb, NL_CB_DUMP_INTR, msg) # NL_CB_CALL(cb, NL_CB_DUMP_INTR, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) else: # We have to continue reading to clear all messages until a NLMSG_DONE is received and report the # inconsistency. interrupted = 1 if hdr.nlmsg_flags & NLM_F_ACK: # Other side wishes to see an ack for this message. if cb.cb_set[NL_CB_SEND_ACK]: err = nl_cb_call(cb, NL_CB_SEND_ACK, msg) # NL_CB_CALL(cb, NL_CB_SEND_ACK, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) if hdr.nlmsg_type == NLMSG_DONE: # Messages terminates a multipart message, this is usually the end of a message and therefore we slip # out of the loop by default. the user may overrule this action by skipping this packet. multipart = 0 if cb.cb_set[NL_CB_FINISH]: err = nl_cb_call(cb, NL_CB_FINISH, msg) # NL_CB_CALL(cb, NL_CB_FINISH, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) elif hdr.nlmsg_type == NLMSG_NOOP: # Message to be ignored, the default action is to skip this message if no callback is specified. The # user may overrule this action by returning NL_PROCEED. if cb.cb_set[NL_CB_SKIPPED]: err = nl_cb_call(cb, NL_CB_SKIPPED, msg) # NL_CB_CALL(cb, NL_CB_SKIPPED, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) else: hdr = nlmsg_next(hdr, n) continue elif hdr.nlmsg_type == NLMSG_OVERRUN: # Data got lost, report back to user. The default action is to quit parsing. The user may overrule this # action by retuning NL_SKIP or NL_PROCEED (dangerous). if cb.cb_set[NL_CB_OVERRUN]: err = nl_cb_call(cb, NL_CB_OVERRUN, msg) # NL_CB_CALL(cb, NL_CB_OVERRUN, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) else: return -NLE_DUMP_INTR if interrupted else -NLE_MSG_OVERFLOW elif hdr.nlmsg_type == NLMSG_ERROR: # Message carries a nlmsgerr. e = nlmsgerr(nlmsg_data(hdr)) if hdr.nlmsg_len < nlmsg_size(e.SIZEOF): # Truncated error message, the default action is to stop parsing. The user may overrule this action # by returning NL_SKIP or NL_PROCEED (dangerous). if cb.cb_set[NL_CB_INVALID]: err = nl_cb_call(cb, NL_CB_INVALID, msg) # NL_CB_CALL(cb, NL_CB_INVALID, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) else: return -NLE_DUMP_INTR if interrupted else -NLE_MSG_TRUNC elif e.error: # Error message reported back from kernel. if cb.cb_err: err = cb.cb_err(nla, e, cb.cb_err_arg) if err < 0: return -NLE_DUMP_INTR if interrupted else err elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else -nl_syserr2nlerr(e.error) else: return -NLE_DUMP_INTR if interrupted else -nl_syserr2nlerr(e.error) elif cb.cb_set[NL_CB_ACK]: err = nl_cb_call(cb, NL_CB_ACK, msg) # NL_CB_CALL(cb, NL_CB_ACK, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) else: # Valid message (not checking for MULTIPART bit to get along with broken kernels. NL_SKIP has no effect # on this. if cb.cb_set[NL_CB_VALID]: err = nl_cb_call(cb, NL_CB_VALID, msg) # NL_CB_CALL(cb, NL_CB_VALID, msg) if err == NL_OK: pass elif err == NL_SKIP: hdr = nlmsg_next(hdr, n) continue elif err == NL_STOP: return -NLE_DUMP_INTR if interrupted else nrecv else: return -NLE_DUMP_INTR if interrupted else (err or nrecv) hdr = nlmsg_next(hdr, n) del buf[:] creds = None if multipart: # Multipart message not yet complete, continue reading. continue err = 0 if interrupted: return -NLE_DUMP_INTR if not err: err = nrecv return err
def info_callback(msg, _): nlh = nlmsg_hdr(msg) gnlh = genlmsghdr(nlmsg_data(nlh)) tb = dict((i, None) for i in range(NCSI_ATTR_MAX + 1)) ret = genlmsg_parse(nlh, 0, tb, NCSI_ATTR_MAX, ncsi_policy) if ret != 0: reason = errmsg[abs(ret)] print("genlmsg_parse returned {}, {}".format(ret, reason)) return ret if not NCSI_ATTR_PACKAGE_LIST in tb: print('No packages!') return -1 rem = c_int() for nla in nla_for_each_nested(tb[NCSI_ATTR_PACKAGE_LIST], rem): ptb = dict() ret = nla_parse_nested(ptb, NCSI_PKG_ATTR_MAX, nla, ncsi_package_policy) if ret < 0: print('Failed to parse package nest') return ret if NCSI_PKG_ATTR_ID in ptb: print('package {}'.format(nla_get_u32(ptb[NCSI_PKG_ATTR_ID]))) else: print('package (with no id?)') if NCSI_PKG_ATTR_FORCED in ptb: print('this package is forced') print('----------') crem = c_int() for cnla in nla_for_each_nested(ptb[NCSI_PKG_ATTR_CHANNEL_LIST], crem): ctb = dict() ret = nla_parse_nested(ctb, NCSI_CHANNEL_ATTR_MAX, cnla, ncsi_channel_policy) if ret < 0: print('Failed to parse channel nest') return ret if NCSI_CHANNEL_ATTR_ID in ctb: channel = nla_get_u32(ctb[NCSI_CHANNEL_ATTR_ID]) if NCSI_CHANNEL_ATTR_ACTIVE in ctb: print('channel {} - active!'.format(channel)) else: print('channel {}'.format(channel)) if NCSI_CHANNEL_ATTR_FORCED in ctb: print('\tthis channel is forced') else: print('channel (with no id?)') if NCSI_CHANNEL_ATTR_VERSION_MAJOR in ctb: print('\tmajor version {}'.format( nla_get_u32(ctb[NCSI_CHANNEL_ATTR_VERSION_MAJOR]))) if NCSI_CHANNEL_ATTR_VERSION_MINOR in ctb: print('\tminor version {}'.format( nla_get_u32(ctb[NCSI_CHANNEL_ATTR_VERSION_MINOR]))) if NCSI_CHANNEL_ATTR_VERSION_STR in ctb: print('\tversion string {}'.format( nla_get_string(ctb[NCSI_CHANNEL_ATTR_VERSION_STR]))) if NCSI_CHANNEL_ATTR_LINK_STATE in ctb: print('\tlink state {}'.format( nla_get_u32(ctb[NCSI_CHANNEL_ATTR_LINK_STATE]))) if NCSI_CHANNEL_ATTR_VLAN_LIST in ctb: print('\tactive vlan ids:') rrem = c_int() vids = ctb[NCSI_CHANNEL_ATTR_VLAN_LIST] vid = nlattr(nla_data(vids)) rrem.value = nla_len(vids) while nla_ok(vid, rrem): print('\t\t{}'.format(nla_get_u16(vid))) vid = nla_next(vid, rrem) return NL_SKIP