tracer_test.go 9.5 KB

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