github.com/elastic/gosigar@v0.14.3/psnotify/psnotify_linux.go (about)

     1  // Copyright (c) 2012 VMware, Inc.
     2  
     3  // Go interface to the Linux netlink process connector.
     4  // See Documentation/connector/connector.txt in the linux kernel source tree.
     5  package psnotify
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"os"
    11  	"syscall"
    12  
    13  	"github.com/elastic/gosigar/sys"
    14  )
    15  
    16  const (
    17  	// internal flags (from <linux/connector.h>)
    18  	_CN_IDX_PROC = 0x1
    19  	_CN_VAL_PROC = 0x1
    20  
    21  	// internal flags (from <linux/cn_proc.h>)
    22  	_PROC_CN_MCAST_LISTEN = 1
    23  	_PROC_CN_MCAST_IGNORE = 2
    24  
    25  	// Flags (from <linux/cn_proc.h>)
    26  	PROC_EVENT_FORK = 0x00000001 // fork() events
    27  	PROC_EVENT_EXEC = 0x00000002 // exec() events
    28  	PROC_EVENT_EXIT = 0x80000000 // exit() events
    29  
    30  	// Watch for all process events
    31  	PROC_EVENT_ALL = PROC_EVENT_FORK | PROC_EVENT_EXEC | PROC_EVENT_EXIT
    32  )
    33  
    34  var (
    35  	byteOrder = sys.GetEndian()
    36  )
    37  
    38  // linux/connector.h: struct cb_id
    39  type cbId struct {
    40  	Idx uint32
    41  	Val uint32
    42  }
    43  
    44  // linux/connector.h: struct cb_msg
    45  type cnMsg struct {
    46  	Id    cbId
    47  	Seq   uint32
    48  	Ack   uint32
    49  	Len   uint16
    50  	Flags uint16
    51  }
    52  
    53  // linux/cn_proc.h: struct proc_event.{what,cpu,timestamp_ns}
    54  type procEventHeader struct {
    55  	What      uint32
    56  	Cpu       uint32
    57  	Timestamp uint64
    58  }
    59  
    60  // linux/cn_proc.h: struct proc_event.fork
    61  type forkProcEvent struct {
    62  	ParentPid  uint32
    63  	ParentTgid uint32
    64  	ChildPid   uint32
    65  	ChildTgid  uint32
    66  }
    67  
    68  // linux/cn_proc.h: struct proc_event.exec
    69  type execProcEvent struct {
    70  	ProcessPid  uint32
    71  	ProcessTgid uint32
    72  }
    73  
    74  // linux/cn_proc.h: struct proc_event.exit
    75  type exitProcEvent struct {
    76  	ProcessPid  uint32
    77  	ProcessTgid uint32
    78  	ExitCode    uint32
    79  	ExitSignal  uint32
    80  }
    81  
    82  // standard netlink header + connector header
    83  type netlinkProcMessage struct {
    84  	Header syscall.NlMsghdr
    85  	Data   cnMsg
    86  }
    87  
    88  type netlinkListener struct {
    89  	addr *syscall.SockaddrNetlink // Netlink socket address
    90  	sock int                      // The syscall.Socket() file descriptor
    91  	seq  uint32                   // struct cn_msg.seq
    92  }
    93  
    94  // Initialize linux implementation of the eventListener interface
    95  func createListener() (eventListener, error) {
    96  	listener := &netlinkListener{}
    97  	err := listener.bind()
    98  	return listener, err
    99  }
   100  
   101  // noop on linux
   102  func (w *Watcher) unregister(pid int) error {
   103  	return nil
   104  }
   105  
   106  // noop on linux
   107  func (w *Watcher) register(pid int, flags uint32) error {
   108  	return nil
   109  }
   110  
   111  // Read events from the netlink socket
   112  func (w *Watcher) readEvents() {
   113  	buf := make([]byte, syscall.Getpagesize())
   114  
   115  	listener, _ := w.listener.(*netlinkListener)
   116  
   117  	for {
   118  		if w.isDone() {
   119  			return
   120  		}
   121  
   122  		nr, _, err := syscall.Recvfrom(listener.sock, buf, 0)
   123  
   124  		if err != nil {
   125  			w.Error <- err
   126  			continue
   127  		}
   128  		if nr < syscall.NLMSG_HDRLEN {
   129  			w.Error <- syscall.EINVAL
   130  			continue
   131  		}
   132  
   133  		msgs, _ := syscall.ParseNetlinkMessage(buf[:nr])
   134  
   135  		for _, m := range msgs {
   136  			if m.Header.Type == syscall.NLMSG_DONE {
   137  				w.handleEvent(m.Data)
   138  			}
   139  		}
   140  	}
   141  }
   142  
   143  // Internal helper to check if pid && event is being watched
   144  func (w *Watcher) isWatching(pid int, event uint32) bool {
   145  	if watch, ok := w.watches[pid]; ok {
   146  		return (watch.flags & event) == event
   147  	}
   148  	return false
   149  }
   150  
   151  // Dispatch events from the netlink socket to the Event channels.
   152  // Unlike bsd kqueue, netlink receives events for all pids,
   153  // so we apply filtering based on the watch table via isWatching()
   154  func (w *Watcher) handleEvent(data []byte) {
   155  	buf := bytes.NewBuffer(data)
   156  	msg := &cnMsg{}
   157  	hdr := &procEventHeader{}
   158  
   159  	binary.Read(buf, byteOrder, msg)
   160  	binary.Read(buf, byteOrder, hdr)
   161  
   162  	switch hdr.What {
   163  	case PROC_EVENT_FORK:
   164  		event := &forkProcEvent{}
   165  		binary.Read(buf, byteOrder, event)
   166  		ppid := int(event.ParentTgid)
   167  		pid := int(event.ChildTgid)
   168  
   169  		if w.isWatching(ppid, PROC_EVENT_EXEC) {
   170  			// follow forks
   171  			watch, _ := w.watches[ppid]
   172  			w.Watch(pid, watch.flags)
   173  		}
   174  
   175  		if w.isWatching(ppid, PROC_EVENT_FORK) {
   176  			w.Fork <- &ProcEventFork{ParentPid: ppid, ChildPid: pid}
   177  		}
   178  	case PROC_EVENT_EXEC:
   179  		event := &execProcEvent{}
   180  		binary.Read(buf, byteOrder, event)
   181  		pid := int(event.ProcessTgid)
   182  
   183  		if w.isWatching(pid, PROC_EVENT_EXEC) {
   184  			w.Exec <- &ProcEventExec{Pid: pid}
   185  		}
   186  	case PROC_EVENT_EXIT:
   187  		event := &exitProcEvent{}
   188  		binary.Read(buf, byteOrder, event)
   189  		pid := int(event.ProcessTgid)
   190  
   191  		if w.isWatching(pid, PROC_EVENT_EXIT) {
   192  			w.RemoveWatch(pid)
   193  			w.Exit <- &ProcEventExit{Pid: pid}
   194  		}
   195  	}
   196  }
   197  
   198  // Bind our netlink socket and
   199  // send a listen control message to the connector driver.
   200  func (listener *netlinkListener) bind() error {
   201  	sock, err := syscall.Socket(
   202  		syscall.AF_NETLINK,
   203  		syscall.SOCK_DGRAM,
   204  		syscall.NETLINK_CONNECTOR)
   205  
   206  	if err != nil {
   207  		return err
   208  	}
   209  
   210  	listener.sock = sock
   211  	listener.addr = &syscall.SockaddrNetlink{
   212  		Family: syscall.AF_NETLINK,
   213  		Groups: _CN_IDX_PROC,
   214  	}
   215  
   216  	err = syscall.Bind(listener.sock, listener.addr)
   217  
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	return listener.send(_PROC_CN_MCAST_LISTEN)
   223  }
   224  
   225  // Send an ignore control message to the connector driver
   226  // and close our netlink socket.
   227  func (listener *netlinkListener) close() error {
   228  	err := listener.send(_PROC_CN_MCAST_IGNORE)
   229  	syscall.Close(listener.sock)
   230  	return err
   231  }
   232  
   233  // Generic method for sending control messages to the connector
   234  // driver; where op is one of PROC_CN_MCAST_{LISTEN,IGNORE}
   235  func (listener *netlinkListener) send(op uint32) error {
   236  	listener.seq++
   237  	pr := &netlinkProcMessage{}
   238  	plen := binary.Size(pr.Data) + binary.Size(op)
   239  	pr.Header.Len = syscall.NLMSG_HDRLEN + uint32(plen)
   240  	pr.Header.Type = uint16(syscall.NLMSG_DONE)
   241  	pr.Header.Flags = 0
   242  	pr.Header.Seq = listener.seq
   243  	pr.Header.Pid = uint32(os.Getpid())
   244  
   245  	pr.Data.Id.Idx = _CN_IDX_PROC
   246  	pr.Data.Id.Val = _CN_VAL_PROC
   247  
   248  	pr.Data.Len = uint16(binary.Size(op))
   249  
   250  	buf := bytes.NewBuffer(make([]byte, 0, pr.Header.Len))
   251  	binary.Write(buf, byteOrder, pr)
   252  	binary.Write(buf, byteOrder, op)
   253  
   254  	return syscall.Sendto(listener.sock, buf.Bytes(), 0, listener.addr)
   255  }