Beispiel #1
0
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)
Beispiel #2
0
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