feat: add proc events support

This commit is contained in:
Steven Falken 2021-07-12 22:22:55 +02:00 committed by Vish (Ishaya) Abrams
parent 76d8bfe0aa
commit dd687eb2f2
4 changed files with 379 additions and 1 deletions

View File

@ -27,7 +27,8 @@ const (
// tc rules or filters, or other more memory requiring data. // tc rules or filters, or other more memory requiring data.
RECEIVE_BUFFER_SIZE = 65536 RECEIVE_BUFFER_SIZE = 65536
// Kernel netlink pid // Kernel netlink pid
PidKernel uint32 = 0 PidKernel uint32 = 0
SizeofCnMsgOp = 0x18
) )
// SupportedNlFamilies contains the list of netlink families this netlink package supports // SupportedNlFamilies contains the list of netlink families this netlink package supports
@ -85,6 +86,56 @@ type NetlinkRequestData interface {
Serialize() []byte Serialize() []byte
} }
const (
PROC_CN_MCAST_LISTEN = 1
PROC_CN_MCAST_IGNORE
)
type CbID struct {
Idx uint32
Val uint32
}
type CnMsg struct {
ID CbID
Seq uint32
Ack uint32
Length uint16
Flags uint16
}
type CnMsgOp struct {
CnMsg
// here we differ from the C header
Op uint32
}
func NewCnMsg(idx, val, op uint32) *CnMsgOp {
var cm CnMsgOp
cm.ID.Idx = idx
cm.ID.Val = val
cm.Ack = 0
cm.Seq = 1
cm.Length = uint16(binary.Size(op))
cm.Op = op
return &cm
}
func (msg *CnMsgOp) Serialize() []byte {
return (*(*[SizeofCnMsgOp]byte)(unsafe.Pointer(msg)))[:]
}
func DeserializeCnMsgOp(b []byte) *CnMsgOp {
return (*CnMsgOp)(unsafe.Pointer(&b[0:SizeofCnMsgOp][0]))
}
func (msg *CnMsgOp) Len() int {
return SizeofCnMsgOp
}
// IfInfomsg is related to links, but it is used for list requests as well // IfInfomsg is related to links, but it is used for list requests as well
type IfInfomsg struct { type IfInfomsg struct {
unix.IfInfomsg unix.IfInfomsg

View File

@ -98,3 +98,35 @@ func TestIfSocketCloses(t *testing.T) {
t.Fatalf("Expected error instead received nil") t.Fatalf("Expected error instead received nil")
} }
} }
func (msg *CnMsgOp) write(b []byte) {
native := NativeEndian()
native.PutUint32(b[0:4], msg.ID.Idx)
native.PutUint32(b[4:8], msg.ID.Val)
native.PutUint32(b[8:12], msg.Seq)
native.PutUint32(b[12:16], msg.Ack)
native.PutUint16(b[16:18], msg.Length)
native.PutUint16(b[18:20], msg.Flags)
native.PutUint32(b[20:24], msg.Op)
}
func (msg *CnMsgOp) serializeSafe() []byte {
length := msg.Len()
b := make([]byte, length)
msg.write(b)
return b
}
func deserializeCnMsgOpSafe(b []byte) *CnMsgOp {
var msg = CnMsgOp{}
binary.Read(bytes.NewReader(b[0:SizeofCnMsgOp]), NativeEndian(), &msg)
return &msg
}
func TestCnMsgOpDeserializeSerialize(t *testing.T) {
var orig = make([]byte, SizeofCnMsgOp)
rand.Read(orig)
safemsg := deserializeCnMsgOpSafe(orig)
msg := DeserializeCnMsgOp(orig)
testDeserializeSerialize(t, orig, safemsg, msg)
}

217
proc_event.go Normal file
View File

@ -0,0 +1,217 @@
package netlink
import (
"bytes"
"encoding/binary"
"fmt"
"os"
"syscall"
"github.com/vishvananda/netlink/nl"
"github.com/vishvananda/netns"
"golang.org/x/sys/unix"
)
const CN_IDX_PROC = 0x1
const (
PROC_EVENT_NONE = 0x00000000
PROC_EVENT_FORK = 0x00000001
PROC_EVENT_EXEC = 0x00000002
PROC_EVENT_UID = 0x00000004
PROC_EVENT_GID = 0x00000040
PROC_EVENT_SID = 0x00000080
PROC_EVENT_PTRACE = 0x00000100
PROC_EVENT_COMM = 0x00000200
PROC_EVENT_COREDUMP = 0x40000000
PROC_EVENT_EXIT = 0x80000000
)
const (
CN_VAL_PROC = 0x1
PROC_CN_MCAST_LISTEN = 0x1
)
type ProcEventMsg interface {
Pid() uint32
Tgid() uint32
}
type ProcEventHeader struct {
What uint32
CPU uint32
Timestamp uint64
}
type ProcEvent struct {
ProcEventHeader
Msg ProcEventMsg
}
func (pe *ProcEvent) setHeader(h ProcEventHeader) {
pe.What = h.What
pe.CPU = h.CPU
pe.Timestamp = h.Timestamp
}
type ExitProcEvent struct {
ProcessPid uint32
ProcessTgid uint32
ExitCode uint32
ExitSignal uint32
ParentPid uint32
ParentTgid uint32
}
type ExitProcEvent2 struct {
ProcessPid uint32
ProcessTgid uint32
ExitCode uint32
ExitSignal uint32
ParentPid uint32
ParentTgid uint32
}
func (e *ExitProcEvent) Pid() uint32 {
return e.ProcessPid
}
func (e *ExitProcEvent) Tgid() uint32 {
return e.ProcessTgid
}
type ExecProcEvent struct {
ProcessPid uint32
ProcessTgid uint32
}
func (e *ExecProcEvent) Pid() uint32 {
return e.ProcessPid
}
func (e *ExecProcEvent) Tgid() uint32 {
return e.ProcessTgid
}
type ForkProcEvent struct {
ParentPid uint32
ParentTgid uint32
ChildPid uint32
ChildTgid uint32
}
func (e *ForkProcEvent) Pid() uint32 {
return e.ParentPid
}
func (e *ForkProcEvent) Tgid() uint32 {
return e.ParentTgid
}
type CommProcEvent struct {
ProcessPid uint32
ProcessTgid uint32
Comm [16]byte
}
func (e *CommProcEvent) Pid() uint32 {
return e.ProcessPid
}
func (e *CommProcEvent) Tgid() uint32 {
return e.ProcessTgid
}
func ProcEventMonitor(ch chan<- ProcEvent, done <-chan struct{}, errorChan chan<- error) error {
h, err := NewHandle()
if err != nil {
return err
}
defer h.Delete()
s, err := nl.SubscribeAt(netns.None(), netns.None(), unix.NETLINK_CONNECTOR, CN_IDX_PROC)
if err != nil {
return err
}
var nlmsg nl.NetlinkRequest
nlmsg.Pid = uint32(os.Getpid())
nlmsg.Type = unix.NLMSG_DONE
nlmsg.Len = uint32(unix.SizeofNlMsghdr)
cm := nl.NewCnMsg(CN_IDX_PROC, CN_VAL_PROC, PROC_CN_MCAST_LISTEN)
nlmsg.AddData(cm)
s.Send(&nlmsg)
if done != nil {
go func() {
<-done
s.Close()
}()
}
go func() {
defer close(ch)
for {
msgs, from, err := s.Receive()
if err != nil {
errorChan <- err
return
}
if from.Pid != nl.PidKernel {
errorChan <- fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel)
return
}
for _, m := range msgs {
e := parseNetlinkMessage(m)
if e != nil {
ch <- *e
}
}
}
}()
return nil
}
func parseNetlinkMessage(m syscall.NetlinkMessage) *ProcEvent {
if m.Header.Type == unix.NLMSG_DONE {
buf := bytes.NewBuffer(m.Data)
msg := &nl.CnMsg{}
hdr := &ProcEventHeader{}
binary.Read(buf, nl.NativeEndian(), msg)
binary.Read(buf, nl.NativeEndian(), hdr)
pe := &ProcEvent{}
pe.setHeader(*hdr)
switch hdr.What {
case PROC_EVENT_EXIT:
event := &ExitProcEvent{}
binary.Read(buf, nl.NativeEndian(), event)
pe.Msg = event
return pe
case PROC_EVENT_FORK:
event := &ForkProcEvent{}
binary.Read(buf, nl.NativeEndian(), event)
pe.Msg = event
return pe
case PROC_EVENT_EXEC:
event := &ExecProcEvent{}
binary.Read(buf, nl.NativeEndian(), event)
pe.Msg = event
return pe
case PROC_EVENT_COMM:
event := &CommProcEvent{}
binary.Read(buf, nl.NativeEndian(), event)
pe.Msg = event
return pe
}
return nil
}
return nil
}

78
proc_event_test.go Normal file
View File

@ -0,0 +1,78 @@
package netlink
import (
"github.com/vishvananda/netns"
"os"
"os/exec"
"runtime"
"testing"
)
func TestSubscribeProcEvent(t *testing.T) {
skipUnlessRoot(t)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
pid1ns, err := netns.GetFromPid(1)
if err != nil {
panic(err)
}
err = netns.Set(pid1ns)
if err != nil {
panic(err)
}
ch := make(chan ProcEvent)
done := make(chan struct{})
defer close(done)
errChan := make(chan error)
if err := ProcEventMonitor(ch, done, errChan); err != nil {
t.Fatal(err)
}
cmd := exec.Command("false")
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
// first we wait for proc - i.e. childTgid is cmd.Process.Pid
for {
e := <-ch
t.Logf("pid: %+v e: %+v", os.Getpid(), e)
if e.Msg.Tgid() == uint32(os.Getpid()) {
if forkEvent, ok := e.Msg.(*ForkProcEvent); ok {
if forkEvent.ChildTgid == uint32(cmd.Process.Pid) {
break
}
}
}
}
// wait for exec event
for {
e := <-ch
if e.Msg.Tgid() == uint32(cmd.Process.Pid) {
if _, ok := e.Msg.(*ExecProcEvent); ok {
break
}
}
}
cmd.Wait()
for {
e := <-ch
if e.Msg.Tgid() == uint32(cmd.Process.Pid) {
if exitEvent, ok := e.Msg.(*ExitProcEvent); ok {
if exitEvent.ExitCode != 256 {
t.Errorf("Expected error code 256 (-1), but got %+v", exitEvent)
}
break
}
}
}
done <- struct{}{}
}