def __listen(): __logger.debug('Connecting to the netlink proc connector') nl_sock = libnl.nl_socket_alloc() if nl_sock == libnl_ffi.NULL: raise RuntimeError('Error allocating nl_sock') try: # Register callbacks libnl_check(libnl.nl_socket_modify_cb(nl_sock, libnl.NL_CB_FINISH, libnl.NL_CB_CUSTOM, __msg_cb, libnl_ffi.NULL)) libnl_check(libnl.nl_socket_modify_err_cb(nl_sock, libnl.NL_CB_CUSTOM, __err_cb, libnl_ffi.NULL)) # Multicast event sequence numbers are not sequential, so do not attempt to verify them libnl.nl_socket_disable_seq_check(nl_sock) # Connect libnl_check(libnl.nl_connect(nl_sock, libnl.NETLINK_CONNECTOR)) try: # Subscribe to the PROC CONNECTOR's multicast group libnl_check(libnl.nl_socket_add_membership(nl_sock, libnl.CN_IDX_PROC)) # Only need to send two messages, so tx buffer can be small libnl_check(libnl.nl_socket_set_buffer_size(nl_sock, pe_nl_rx_buffer_size, 128)) # Increment the PROC CONNECTOR's internal listener counter to ensure that it sends messages. # This must be sent after we subscribe to the multicast group so that we can use the ACK to # trigger the "events_good" event. (See the notes in libnl.py about the PROC CONNECTOR's # internal counter.) cn_proc_msg = libnl_ffi.new('struct cn_proc_msg *') # libnl_ffi.new() calls memset(0) for us cn_proc_msg.cn_msg.id.idx = libnl.CN_IDX_PROC; cn_proc_msg.cn_msg.id.val = libnl.CN_VAL_PROC; cn_proc_msg.cn_msg.len = libnl_ffi.sizeof('enum proc_cn_mcast_op') cn_proc_msg.cn_mcast = libnl.PROC_CN_MCAST_LISTEN cn_proc_msg_size = libnl_ffi.sizeof('struct cn_proc_msg') libnl_check(libnl.nl_send_simple(nl_sock, libnl.NLMSG_DONE, 0, cn_proc_msg, cn_proc_msg_size)) try: # Use non-blocking mode so we can wake select() with a signal or a timeout # In blocking mode, nl_recv() loops on signals, and we have no way to stop that loop libnl_check(libnl.nl_socket_set_nonblocking(nl_sock)) nl_sock_fd = libnl.nl_socket_get_fd(nl_sock) # We can only wake select() with a signal if we know the ID of this thread # Otherwise we have to periodically wake select() with a timeout to determine when to exit if __thread_id < 0: __logger.info('Thread ID not available, will periodically wake select() to determine when to exit') select_timeout = 3 else: select_timeout = None __logger.debug('Connected to the netlink proc connector') while not __exit: try: r, w, x = select.select([nl_sock_fd], [], [nl_sock_fd], select_timeout) except select.error as e: err_num, err_str = e.args if err_num == errno.EINTR: continue # Woken by a signal raise RuntimeError('select() returned error: {0}'.format(e)) if len(r) == 0 and len(x) == 0: continue # Timeout err_num = libnl.nl_recvmsgs_default(nl_sock) if err_num == -libnl.NLE_AGAIN: continue if err_num == -libnl.NLE_NOMEM: # See the notes in libnl.py about NLMSG_OVERRUN handlers['events_lost']() continue libnl_check(err_num) # Throw an exception on other errors finally: __logger.debug('Disconnecting from the netlink proc connector') global events_good events_good = False if not __exit: handlers['events_failed']() # If we're here because nl_recvmsgs() or select() failed then this probably won't work, but # we will try it anyway and ignore any errors. Since the socket is in non-blocking mode, # you might think we need to check for NLE_AGAIN, however the 128 byte TX buffer configured # above should be large enough to hold both of the messages we send, so NLE_AGAIN should # never happen. cn_proc_msg.cn_mcast = libnl.PROC_CN_MCAST_IGNORE libnl.nl_send_simple(nl_sock, libnl.NLMSG_DONE, 0, cn_proc_msg, cn_proc_msg_size) finally: libnl.nl_close(nl_sock) finally: libnl.nl_socket_free(nl_sock)
def __msg_cb(msg, arg): # Extract the netlink message (Already validated by libnl) nl_msg_hdr = libnl.nlmsg_hdr(msg) # Validate the netlink message's payload length if nl_msg_hdr.nlmsg_len < libnl.nlmsg_size(libnl_ffi.sizeof('struct cn_msg')): __logger.warn('Received a short NETLINK_CONNECTOR message, will ignore and continue (Expected {0} bytes but got {1} bytes)'.format(libnl.nlmsg_size(libnl_ffi.sizeof('struct cn_msg')), nl_msg_hdr.nlmsg_len)) return libnl.NL_SKIP # Extract and validate the NETLINK_CONNECTOR message # cn_msg.seq should match nl_msg_hdr.nlmsg_seq, but we don't really need to validate it # cn_msg.flags is not used by the PROC CONNECTOR cn_msg = libnl_ffi.cast('struct cn_msg *', libnl.nlmsg_data(nl_msg_hdr)) if cn_msg.id.idx != libnl.CN_IDX_PROC or cn_msg.id.val != libnl.CN_VAL_PROC: __logger.warn('Received a NETLINK_CONNECTOR message with an unexpected ID, will ignore and continue (Expected idx:{0} val:{1} but got idx:{2} val:{3}) (See /usr/include/linux/connector.h)'.format(cn_msg.id.idx, cn_msg.id.val, libnl.CN_IDX_PROC, libnl.CN_VAL_PROC)) return libnl.NL_SKIP # Validate the NETLINK_CONNECTOR message's payload length if cn_msg.len < libnl_ffi.sizeof('struct proc_event'): __logger.warn('Received a short PROC CONNECTOR event, will ignore and continue (Expected {0} bytes but got {1} bytes)'.format(libnl_ffi.sizeof('struct proc_event'), cn_msg.len)) return libnl.NL_SKIP if nl_msg_hdr.nlmsg_len < libnl.nlmsg_size(libnl_ffi.sizeof('struct cn_proc_reply')): __logger.warn('Received a NETLINK message with valid payload length but invalid message length, will ignore and continue (Expected {0} bytes but got {1} bytes)'.format(libnl.nlmsg_size(libnl_ffi.sizeof('struct cn_proc_reply')), nl_msg_hdr.nlmsg_len)) return libnl.NL_SKIP # Extract and validate the PROC CONNECTOR event event = libnl_ffi.cast('struct cn_proc_reply *', libnl.nlmsg_data(nl_msg_hdr)).event if (cn_msg.ack != 0 and event.what != libnl.PROC_EVENT_NONE) or \ (cn_msg.ack == 0 and event.what == libnl.PROC_EVENT_NONE): __logger.warn("Received a PROC CONNECTOR event with an unexpected combination of 'ack' and 'what' values, will ignore and continue (ack: {0} what: {1})".format(cn_msg.ack, event.what)) return libnl.NL_SKIP ev_type = event.what ev_data = event.event_data # If the ability to receive events has not been established or was lost, it looks like things are # working now. global events_good if not events_good: events_good = True handlers['events_good']() # Parse the PROC CONNECTOR event (See /usr/include/linux/cn_proc.h) if ev_type == libnl.PROC_EVENT_NONE: # ACK in response to PROC_CN_MCAST_LISTEN or PROC_CN_MCAST_IGNORE message, don't fire an event if ev_data.ack.err != 0: __logger.warn('Received a PROC CONNECTOR ACK message with error code {0}'.format(ev_data.ack.err)) else: __logger.debug('Received a PROC CONNECTOR ACK message') elif ev_type == libnl.PROC_EVENT_FORK: # Process has been created via fork() handlers['fork']( pid = ev_data.fork.child_tgid, tid = ev_data.fork.child_pid, parent_pid = ev_data.fork.parent_tgid, parent_tid = ev_data.fork.parent_pid, ) elif ev_type == libnl.PROC_EVENT_EXEC: # Process has been replaced via exec() handlers['exec']( # 'exec' is a python keyword, so we have to use getattr(ev_data,'exec') instead of # ev_data.exec pid = getattr(ev_data,'exec').process_tgid, tid = getattr(ev_data,'exec').process_pid, ) elif ev_type == libnl.PROC_EVENT_UID: # Process UID changed handlers['uid']( pid = ev_data.id.process_tgid, tid = ev_data.id.process_pid, real_uid = ev_data.id.r.ruid, effective_uid = ev_data.id.e.euid, ) elif ev_type == libnl.PROC_EVENT_GID: # Process GID changed handlers['gid']( pid = ev_data.id.process_tgid, tid = ev_data.id.process_pid, real_gid = ev_data.id.r.rgid, effective_gid = ev_data.id.e.egid, ) elif ev_type == libnl.PROC_EVENT_SID: # Process has become a session leader # See http://lwn.net/Articles/337708/ handlers['sid']( pid = ev_data.sid.process_tgid, tid = ev_data.sid.process_pid, ) elif hasattr(libnl, 'PROC_EVENT_PTRACE') and ev_type == libnl.PROC_EVENT_PTRACE: # ptrace() has attached to process handlers['ptrace']( pid = ev_data.ptrace.process_tgid, tid = ev_data.ptrace.process_pid, tracer_pid = ev_data.ptrace.tracer_tgid, tracer_tid = ev_data.ptrace.tracer_pid, ) elif hasattr(libnl, 'PROC_EVENT_COMM') and ev_type == libnl.PROC_EVENT_COMM: # Process command name has changed # See https://lkml.org/lkml/2011/8/2/276 handlers['comm']( pid = ev_data.comm.process_tgid, tid = ev_data.comm.process_pid, command = libnl_ffi.string(ev_data.comm.comm), ) elif hasattr(libnl, 'PROC_EVENT_COREDUMP') and ev_type == libnl.PROC_EVENT_COREDUMP: # Process has dumped a core file handlers['coredump']( pid = ev_data.coredump.process_tgid, tid = ev_data.coredump.process_pid, ) elif ev_type == libnl.PROC_EVENT_EXIT: # Process has exited handlers['exit']( pid = ev_data.exit.process_tgid, tid = ev_data.exit.process_pid, exit_status = ev_data.exit.exit_code, exit_signal = ev_data.exit.exit_signal, ) else: __logger.debug("Received a PROC CONNECTOR event with an unknown 'what' value, will ignore and continue ({0}) (See /usr/include/linux/cn_proc.h)".format(event.what)) return libnl.NL_SKIP return libnl.NL_OK