def nl_sendmsg(sk, msg, hdr): """Transmit Netlink message using socket.sendmsg|sendto|send(). https://github.com/thom311/libnl/blob/libnl3_2_25/lib/nl.c#L299 Transmits the message specified in `hdr` over the Netlink socket using Python's socket.sendmsg(). ATTENTION: The `msg` argument will *not* be used to derive the message payload that is being sent out. The `msg` argument is *only* passed on to the `NL_CB_MSG_OUT` callback. The caller is responsible to initialize the `hdr` struct properly and have it point to the message payload and socket address. This function uses `nlmsg_set_src()` to modify the `msg` argument prior to invoking the `NL_CB_MSG_OUT` callback to provide the local port number. This function triggers the `NL_CB_MSG_OUT` callback. ATTENTION: Think twice before using this function. It provides a low level access to the Netlink socket. Among other limitations, it does not add credentials even if enabled or respect the destination address specified in the `msg` object. Positional arguments: sk -- Netlink socket (nl_sock class instance). msg -- Netlink message to be sent (nl_msg class instance). hdr -- sendmsg() message header (msghdr class instance). Returns: Number of bytes sent on success or a negative error code. """ if sk.s_fd < 0: return -NLE_BAD_SOCK nlmsg_set_src(msg, sk.s_local) cb = sk.s_cb if cb.cb_set[NL_CB_MSG_OUT]: ret = nl_cb_call(cb, NL_CB_MSG_OUT, msg) if ret != NL_OK: return ret if hdr.msg_name is None: address = None else: address = tuple(hdr.msg_name) if address == (0, 0) or address == sk.socket_instance.getsockname: address = None try: if hdr.msg_control: ret = sk.socket_instance.sendmsg([hdr.msg_iov], hdr.msg_control, hdr.msg_flags, address) elif address: ret = sk.socket_instance.sendto(hdr.msg_iov, hdr.msg_flags, address) else: ret = sk.socket_instance.send(hdr.msg_iov, hdr.msg_flags) except OSError as exc: return -nl_syserr2nlerr(exc.errno) _LOGGER.debug('sent %d bytes', ret) return ret
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 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