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 }