tracer_test.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. package ebpftracer
  2. import (
  3. "bytes"
  4. "fmt"
  5. "github.com/containerd/cgroups"
  6. "github.com/opencontainers/runtime-spec/specs-go"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. "golang.org/x/sys/unix"
  10. "net"
  11. "os"
  12. "os/exec"
  13. "path"
  14. "strconv"
  15. "strings"
  16. "syscall"
  17. "testing"
  18. "time"
  19. )
  20. func skipIfNotRoot(t *testing.T) {
  21. if os.Getuid() != 0 {
  22. t.SkipNow()
  23. }
  24. }
  25. func TestProcessEvents(t *testing.T) {
  26. skipIfNotRoot(t)
  27. src := `
  28. package main
  29. import (
  30. "bytes"
  31. "os"
  32. "strconv"
  33. "time"
  34. )
  35. func main() {
  36. mb, _ := strconv.Atoi(os.Args[1])
  37. sleep, _ := time.ParseDuration(os.Args[2])
  38. bytes.Repeat([]byte("x"), mb*1024*1024)
  39. time.Sleep(sleep)
  40. }
  41. `
  42. program := path.Join(t.TempDir(), "program")
  43. require.NoError(t, os.WriteFile(program+".go", []byte(src), 0644))
  44. require.NoError(t, exec.Command("go", "build", "-o", program, program+".go").Run())
  45. getEvent, stop := runTracer(t, false)
  46. defer stop()
  47. for {
  48. if e := getEvent(); e == nil {
  49. break
  50. }
  51. }
  52. p1 := exec.Command(program, "600", "10s")
  53. require.NoError(t, p1.Start())
  54. time.Sleep(time.Second)
  55. assert.Equal(t, Event{Type: EventTypeProcessStart, Pid: uint32(p1.Process.Pid)}, *getEvent())
  56. // p1 should be killed by the OOM killer, because VM have only 1 GB of memory total
  57. p2 := exec.Command(program, "400", "1s")
  58. require.NoError(t, p2.Run())
  59. assert.Equal(t, Event{Type: EventTypeProcessStart, Pid: uint32(p2.Process.Pid)}, *getEvent())
  60. require.Error(t, p1.Wait())
  61. assert.Equal(t, Event{Type: EventTypeProcessExit, Reason: EventReasonOOMKill, Pid: uint32(p1.Process.Pid)}, *getEvent())
  62. assert.Equal(t, Event{Type: EventTypeProcessExit, Pid: uint32(p2.Process.Pid)}, *getEvent())
  63. var limit int64 = 200 * 1024 * 1024
  64. control, err := cgroups.New(cgroups.V1, cgroups.StaticPath("/program"), &specs.LinuxResources{
  65. Memory: &specs.LinuxMemory{Limit: &limit},
  66. })
  67. require.NoError(t, err)
  68. defer control.Delete()
  69. // p3 should be killed by the OOM killer, because 300 MB > 200 MB cgroup limit
  70. p3 := exec.Command("cgexec", "-g", "memory:program", program, "300", "1s")
  71. require.Error(t, p3.Run())
  72. assert.Equal(t, Event{Type: EventTypeProcessStart, Pid: uint32(p3.Process.Pid)}, *getEvent())
  73. assert.Equal(t, Event{Type: EventTypeProcessExit, Reason: EventReasonOOMKill, Pid: uint32(p3.Process.Pid)}, *getEvent())
  74. for {
  75. e := getEvent()
  76. if e == nil {
  77. break
  78. }
  79. t.Errorf("unexpected event %+v", e)
  80. }
  81. }
  82. func TestTcpEvents(t *testing.T) {
  83. skipIfNotRoot(t)
  84. l, err := net.Listen("tcp", "127.0.0.1:8080")
  85. require.NoError(t, err)
  86. listenAddr := l.Addr().String()
  87. remoteAddr := "127.0.0.1:8080"
  88. c, err := net.DialTimeout("tcp", remoteAddr, 100*time.Millisecond)
  89. require.NoError(t, err)
  90. localAddr := c.LocalAddr().String()
  91. time.Sleep(100 * time.Millisecond)
  92. getEvent, stop := runTracer(t, false)
  93. defer stop()
  94. pid := uint32(os.Getpid())
  95. is := func(e *Event, typ EventType, sAddr string, dAddr string, pid uint32) bool {
  96. if e == nil {
  97. return false
  98. }
  99. sa := e.SrcAddr.String()
  100. if strings.HasSuffix(sAddr, ":") {
  101. sa = fmt.Sprintf("%s:", e.SrcAddr.IP())
  102. }
  103. da := e.DstAddr.String()
  104. return e.Type == typ && e.Pid == pid && sa == sAddr && da == dAddr
  105. }
  106. listenFound := false
  107. connectFound := false
  108. for {
  109. e := getEvent()
  110. if e == nil {
  111. break
  112. }
  113. if is(e, EventTypeListenOpen, listenAddr, "0.0.0.0:0", pid) {
  114. listenFound = true
  115. }
  116. if is(e, EventTypeConnectionOpen, localAddr, remoteAddr, pid) {
  117. connectFound = true
  118. }
  119. }
  120. if !listenFound {
  121. t.Errorf("expected %s on %s", EventTypeListenOpen, l.Addr())
  122. }
  123. if !connectFound {
  124. t.Errorf("expected %s to %s", EventTypeConnectionOpen, l.Addr())
  125. }
  126. nextIs := func(typ EventType, sAddr string, dAddr string, pid uint32) {
  127. e := getEvent()
  128. if !is(e, typ, sAddr, dAddr, pid) {
  129. expected := fmt.Sprintf("%-20s %6d: %s -> %s", typ, pid, sAddr, dAddr)
  130. actual := "nil"
  131. if e != nil {
  132. actual = fmt.Sprintf("%-20s %6d: %s -> %s", e.Type, e.Pid, e.SrcAddr, e.DstAddr)
  133. }
  134. assert.Equal(t, expected, actual)
  135. }
  136. }
  137. require.NoError(t, c.Close())
  138. nextIs(EventTypeConnectionClose, localAddr, listenAddr, 0)
  139. nextIs(EventTypeConnectionClose, listenAddr, localAddr, 0)
  140. require.NoError(t, l.Close())
  141. nextIs(EventTypeListenClose, listenAddr, "0.0.0.0:0", pid)
  142. c, err = net.DialTimeout("tcp", listenAddr, 100*time.Millisecond)
  143. require.Error(t, err)
  144. nextIs(EventTypeConnectionError, "127.0.0.1:", listenAddr, pid)
  145. l, err = net.Listen("tcp4", ":8080")
  146. require.NoError(t, err)
  147. listenAddr = l.Addr().String()
  148. nextIs(EventTypeListenOpen, listenAddr, "0.0.0.0:0", pid)
  149. c, err = net.DialTimeout("tcp", remoteAddr, 100*time.Millisecond)
  150. require.NoError(t, err)
  151. localAddr = c.LocalAddr().String()
  152. nextIs(EventTypeConnectionOpen, localAddr, remoteAddr, pid)
  153. require.NoError(t, exec.Command("tc", "qdisc", "add", "dev", "lo", "root", "netem", "loss", "100%").Run())
  154. getEvent()
  155. getEvent()
  156. c.Write([]byte("hello"))
  157. nextIs(EventTypeTCPRetransmit, localAddr, remoteAddr, 0)
  158. require.NoError(t, exec.Command("tc", "qdisc", "del", "dev", "lo", "root", "netem").Run())
  159. getEvent()
  160. getEvent()
  161. func() {
  162. timer := time.NewTimer(time.Second)
  163. for {
  164. select {
  165. case <-timer.C:
  166. return
  167. default:
  168. e := getEvent()
  169. require.True(t, e == nil || e.Type == EventTypeTCPRetransmit)
  170. }
  171. }
  172. }()
  173. require.NoError(t, c.Close())
  174. nextIs(EventTypeConnectionClose, localAddr, remoteAddr, 0)
  175. nextIs(EventTypeConnectionClose, remoteAddr, localAddr, 0)
  176. require.NoError(t, l.Close())
  177. nextIs(EventTypeListenClose, listenAddr, "0.0.0.0:0", pid)
  178. for {
  179. e := getEvent()
  180. if e == nil {
  181. break
  182. }
  183. t.Errorf("unexpected event %+v", e)
  184. }
  185. }
  186. func TestFileEvents(t *testing.T) {
  187. skipIfNotRoot(t)
  188. src := `
  189. package main
  190. import (
  191. "os"
  192. "strconv"
  193. "syscall"
  194. "unsafe"
  195. "time"
  196. )
  197. func main() {
  198. call, _ := strconv.Atoi(os.Args[1])
  199. path := os.Args[2]
  200. flags, _ := strconv.Atoi(os.Args[3])
  201. filename, _ := syscall.BytePtrFromString(path)
  202. var err syscall.Errno
  203. switch call {
  204. case syscall.SYS_OPEN:
  205. _, _, err = syscall.Syscall6(syscall.SYS_OPEN, uintptr(unsafe.Pointer(filename)), uintptr(flags), 0, 0, 0, 0)
  206. case syscall.SYS_OPENAT:
  207. AT_FDCWD := -100
  208. _, _, err = syscall.Syscall6(syscall.SYS_OPENAT, uintptr(AT_FDCWD), uintptr(unsafe.Pointer(filename)), uintptr(flags), 0, 0, 0)
  209. }
  210. time.Sleep(100 * time.Millisecond)
  211. os.Exit(int(err))
  212. }
  213. `
  214. require.NoError(t, os.Chdir(t.TempDir()))
  215. require.NoError(t, os.WriteFile("program.go", []byte(src), 0644))
  216. out, err := exec.Command("go", "build", "-o", "program", "program.go").CombinedOutput()
  217. require.Equal(t, "", string(out))
  218. require.NoError(t, err)
  219. getEvent, stop := runTracer(t, false)
  220. defer stop()
  221. for {
  222. if e := getEvent(); e == nil {
  223. break
  224. }
  225. }
  226. for _, call := range []int{syscall.SYS_OPEN, syscall.SYS_OPENAT} {
  227. run := func(file string, flag int) (uint32, error) {
  228. p := exec.Command("./program", strconv.Itoa(call), file, strconv.Itoa(flag))
  229. err := p.Run()
  230. return uint32(p.Process.Pid), err
  231. }
  232. pid, err := run("program.go", os.O_RDONLY)
  233. assert.NoError(t, err)
  234. assert.Equal(t, Event{Type: EventTypeProcessStart, Pid: pid}, *getEvent())
  235. assert.Equal(t, Event{Type: EventTypeProcessExit, Pid: pid}, *getEvent())
  236. pid, err = run("program.go", os.O_WRONLY)
  237. assert.NoError(t, err)
  238. assert.Equal(t, Event{Type: EventTypeProcessStart, Pid: pid}, *getEvent())
  239. assert.Equal(t, Event{Type: EventTypeFileOpen, Pid: pid, Fd: 3}, *getEvent())
  240. assert.Equal(t, Event{Type: EventTypeProcessExit, Pid: pid}, *getEvent())
  241. pid, err = run("program.go", os.O_RDWR)
  242. assert.NoError(t, err)
  243. assert.Equal(t, Event{Type: EventTypeProcessStart, Pid: pid}, *getEvent())
  244. assert.Equal(t, Event{Type: EventTypeFileOpen, Pid: pid, Fd: 3}, *getEvent())
  245. assert.Equal(t, Event{Type: EventTypeProcessExit, Pid: pid}, *getEvent())
  246. // open error: text file busy
  247. pid, err = run("program", os.O_RDWR)
  248. assert.Error(t, err)
  249. assert.Equal(t, Event{Type: EventTypeProcessStart, Pid: pid}, *getEvent())
  250. assert.Equal(t, Event{Type: EventTypeProcessExit, Pid: pid}, *getEvent())
  251. // ignoring /proc/*, /dev/*, /sys/*
  252. for _, f := range []string{"/proc/sys/fs/file-max", "/dev/null", "/sys/kernel/profiling"} {
  253. pid, err = run(f, os.O_RDWR)
  254. assert.NoError(t, err)
  255. assert.Equal(t, Event{Type: EventTypeProcessStart, Pid: pid}, *getEvent())
  256. assert.Equal(t, Event{Type: EventTypeProcessExit, Pid: pid}, *getEvent())
  257. }
  258. for {
  259. e := getEvent()
  260. if e == nil {
  261. break
  262. }
  263. t.Errorf("unexpected event %+v", e)
  264. }
  265. }
  266. }
  267. func runTracer(t *testing.T, verbose bool) (func() *Event, func()) {
  268. events := make(chan Event, 1000)
  269. done := make(chan bool, 1)
  270. var uname unix.Utsname
  271. assert.NoError(t, unix.Uname(&uname))
  272. go func() {
  273. tt, err := NewTracer(events, string(bytes.Split(uname.Release[:], []byte{0})[0]))
  274. require.NoError(t, err)
  275. <-done
  276. tt.Close()
  277. }()
  278. stop := func() {
  279. done <- true
  280. }
  281. get := func() *Event {
  282. select {
  283. case e := <-events:
  284. if verbose {
  285. fmt.Printf("%+v\n", e)
  286. }
  287. return &e
  288. case <-time.NewTimer(time.Second).C:
  289. return nil
  290. }
  291. }
  292. return get, stop
  293. }