inspektor-gadget
443 строки · 12.2 Кб
1//go:build linux
2// +build linux
3
4// Copyright 2023 The Inspektor Gadget authors
5//
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18package socketenricher19
20import (21"fmt"22"net"23"reflect"24"testing"25"unsafe"26
27"golang.org/x/sys/unix"28
29utilstest "github.com/inspektor-gadget/inspektor-gadget/internal/test"30)
31
32func TestSocketEnricherCreate(t *testing.T) {33t.Parallel()34
35utilstest.RequireRoot(t)36utilstest.HostInit(t)37
38tracer, err := NewSocketEnricher()39if err != nil {40t.Fatal(err)41}42if tracer == nil {43t.Fatal("Returned tracer was nil")44}45}
46
47func TestSocketEnricherStopIdempotent(t *testing.T) {48t.Parallel()49
50utilstest.RequireRoot(t)51utilstest.HostInit(t)52
53tracer, _ := NewSocketEnricher()54
55// Check that a double stop doesn't cause issues56tracer.Close()57tracer.Close()58}
59
60type sockOpt struct {61level int62opt int63value int64}
65
66type socketEnricherMapEntry struct {67Key socketenricherSocketsKey
68Value socketenricherSocketsValue
69}
70
71func TestSocketEnricherBind(t *testing.T) {72t.Parallel()73
74utilstest.RequireRoot(t)75utilstest.HostInit(t)76
77type testDefinition struct {78runnerConfig *utilstest.RunnerConfig79generateEvent func() (uint16, int, error)80expectedEvent func(info *utilstest.RunnerInfo, port uint16) *socketEnricherMapEntry81}82
83stringToSlice := func(s string) (ret [16]int8) {84for i := 0; i < 16; i++ {85if i >= len(s) {86break87}88ret[i] = int8(s[i])89}90return91}92
93for name, test := range map[string]testDefinition{94"udp": {95generateEvent: bindSocketFn("127.0.0.1", unix.AF_INET, unix.SOCK_DGRAM, 0),96expectedEvent: func(info *utilstest.RunnerInfo, port uint16) *socketEnricherMapEntry {97return &socketEnricherMapEntry{98Key: socketenricherSocketsKey{99Netns: uint32(info.NetworkNsID),100Family: unix.AF_INET,101Proto: unix.IPPROTO_UDP,102Port: port,103},104Value: socketenricherSocketsValue{105Mntns: info.MountNsID,106PidTgid: uint64(uint32(info.Pid))<<32 + uint64(info.Tid),107Task: stringToSlice("socketenricher."),108},109}110},111},112"udp6": {113generateEvent: bindSocketFn("::", unix.AF_INET6, unix.SOCK_DGRAM, 0),114expectedEvent: func(info *utilstest.RunnerInfo, port uint16) *socketEnricherMapEntry {115return &socketEnricherMapEntry{116Key: socketenricherSocketsKey{117Netns: uint32(info.NetworkNsID),118Family: unix.AF_INET6,119Proto: unix.IPPROTO_UDP,120Port: port,121},122Value: socketenricherSocketsValue{123Mntns: info.MountNsID,124PidTgid: uint64(uint32(info.Pid))<<32 + uint64(info.Tid),125Task: stringToSlice("socketenricher."),126},127}128},129},130"udp6-only": {131generateEvent: func() (uint16, int, error) {132opts := []sockOpt{133{unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, 1},134}135return bindSocketWithOpts("::", unix.AF_INET6, unix.SOCK_DGRAM, 0, opts)136},137expectedEvent: func(info *utilstest.RunnerInfo, port uint16) *socketEnricherMapEntry {138return &socketEnricherMapEntry{139Key: socketenricherSocketsKey{140Netns: uint32(info.NetworkNsID),141Family: unix.AF_INET6,142Proto: unix.IPPROTO_UDP,143Port: port,144},145Value: socketenricherSocketsValue{146Mntns: info.MountNsID,147PidTgid: uint64(uint32(info.Pid))<<32 + uint64(info.Tid),148Task: stringToSlice("socketenricher."),149Ipv6only: int8(1),150},151}152},153},154"tcp": {155generateEvent: bindSocketFn("127.0.0.1", unix.AF_INET, unix.SOCK_STREAM, 0),156expectedEvent: func(info *utilstest.RunnerInfo, port uint16) *socketEnricherMapEntry {157return &socketEnricherMapEntry{158Key: socketenricherSocketsKey{159Netns: uint32(info.NetworkNsID),160Family: unix.AF_INET,161Proto: unix.IPPROTO_TCP,162Port: port,163},164Value: socketenricherSocketsValue{165Mntns: info.MountNsID,166PidTgid: uint64(uint32(info.Pid))<<32 + uint64(info.Tid),167Task: stringToSlice("socketenricher."),168},169}170},171},172"tcp6": {173generateEvent: bindSocketFn("::", unix.AF_INET6, unix.SOCK_STREAM, 0),174expectedEvent: func(info *utilstest.RunnerInfo, port uint16) *socketEnricherMapEntry {175return &socketEnricherMapEntry{176Key: socketenricherSocketsKey{177Netns: uint32(info.NetworkNsID),178Family: unix.AF_INET6,179Proto: unix.IPPROTO_TCP,180Port: port,181},182Value: socketenricherSocketsValue{183Mntns: info.MountNsID,184PidTgid: uint64(uint32(info.Pid))<<32 + uint64(info.Tid),185Task: stringToSlice("socketenricher."),186},187}188},189},190"tcp6-only": {191generateEvent: func() (uint16, int, error) {192opts := []sockOpt{193{unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, 1},194}195return bindSocketWithOpts("::", unix.AF_INET6, unix.SOCK_STREAM, 0, opts)196},197expectedEvent: func(info *utilstest.RunnerInfo, port uint16) *socketEnricherMapEntry {198return &socketEnricherMapEntry{199Key: socketenricherSocketsKey{200Netns: uint32(info.NetworkNsID),201Family: unix.AF_INET6,202Proto: unix.IPPROTO_TCP,203Port: port,204},205Value: socketenricherSocketsValue{206Mntns: info.MountNsID,207PidTgid: uint64(uint32(info.Pid))<<32 + uint64(info.Tid),208Task: stringToSlice("socketenricher."),209Ipv6only: int8(1),210},211}212},213},214"tcp_uid_gid": {215runnerConfig: &utilstest.RunnerConfig{Uid: 1000, Gid: 1111},216generateEvent: bindSocketFn("127.0.0.1", unix.AF_INET, unix.SOCK_STREAM, 0),217expectedEvent: func(info *utilstest.RunnerInfo, port uint16) *socketEnricherMapEntry {218return &socketEnricherMapEntry{219Key: socketenricherSocketsKey{220Netns: uint32(info.NetworkNsID),221Family: unix.AF_INET,222Proto: unix.IPPROTO_TCP,223Port: port,224},225Value: socketenricherSocketsValue{226Mntns: info.MountNsID,227PidTgid: uint64(uint32(info.Pid))<<32 + uint64(info.Tid),228UidGid: uint64(1111)<<32 + uint64(1000),229Task: stringToSlice("socketenricher."),230},231}232},233},234} {235test := test236
237t.Run(name, func(t *testing.T) {238t.Parallel()239
240runner := utilstest.NewRunnerWithTest(t, test.runnerConfig)241
242// We will test 2 scenarios with 2 different tracers:243// 1. earlyTracer will be started before the event is generated244// 2. lateTracer will be started after the event is generated245earlyTracer, err := NewSocketEnricher()246if err != nil {247t.Fatal(err)248}249t.Cleanup(earlyTracer.Close)250
251// Generate the event in the fake container252var port uint16253var fd int254utilstest.RunWithRunner(t, runner, func() error {255var err error256port, fd, err = test.generateEvent()257
258t.Cleanup(func() {259// cleanup only if it has not been already closed260if fd != -1 {261unix.Close(fd)262}263})264
265return err266})267
268// Start the late tracer after the event has been generated269lateTracer, err := NewSocketEnricher()270if err != nil {271t.Fatal(err)272}273t.Cleanup(lateTracer.Close)274
275earlyNormalize := func(entry *socketEnricherMapEntry) {276entry.Value.Sock = 0277}278lateNormalize := func(entry *socketEnricherMapEntry) {279earlyNormalize(entry)280
281// Remove tid: the late tracer cannot distinguish between threads282entry.Value.PidTgid = 0xffffffff00000000 & entry.Value.PidTgid283
284// Our fake container is just a thread in a different MountNsID285// But the late tracer cannot distinguish threads.286if entry.Value.Mntns > 0 {287entry.Value.Mntns = 1288}289
290// We're not able to test uid and gid in the late tracer because our291// fake container is just another thread running on the same process292// and that tracer cannot distinguish threads.293entry.Value.UidGid = 0294}295
296t.Logf("Testing if early tracer noticed the event")297entries := socketsMapEntries(t, earlyTracer, earlyNormalize, nil)298utilstest.ExpectAtLeastOneEvent(test.expectedEvent)(t, runner.Info, port, entries)299
300t.Logf("Testing if late tracer noticed the event")301entries2 := socketsMapEntries(t, lateTracer, lateNormalize, nil)302expectedEvent2 := func(info *utilstest.RunnerInfo, port uint16) *socketEnricherMapEntry {303e := test.expectedEvent(info, port)304lateNormalize(e)305return e306}307utilstest.ExpectAtLeastOneEvent(expectedEvent2)(t, runner.Info, port, entries2)308
309t.Logf("Close socket in order to check for cleanup")310if fd != -1 {311unix.Close(fd)312// Disable t.Cleanup() above313fd = -1314}315
316filter := func(e *socketEnricherMapEntry) bool {317expected := test.expectedEvent(runner.Info, port)318return !reflect.DeepEqual(expected, e)319}320
321t.Logf("Testing if entry is cleaned properly in early tracer")322entries = socketsMapEntries(t, earlyTracer, earlyNormalize, filter)323if len(entries) != 0 {324t.Fatalf("Entry not cleaned properly: %+v", entries)325}326
327t.Logf("Testing if entry is cleaned properly in late tracer")328entries2 = socketsMapEntries(t, lateTracer, lateNormalize, filter)329if len(entries2) != 0 {330t.Fatalf("Entry for late tracer not cleaned properly: %+v", entries2)331}332})333}334}
335
336func socketsMapEntries(337t *testing.T,338tracer *SocketEnricher,339normalize func(entry *socketEnricherMapEntry),340filter func(*socketEnricherMapEntry) bool,341) (entries []socketEnricherMapEntry) {342iter := tracer.SocketsMap().Iterate()343var key socketenricherSocketsKey344var value socketenricherSocketsValue345for iter.Next(&key, &value) {346entry := socketEnricherMapEntry{347Key: key,348Value: value,349}350
351normalize(&entry)352
353if filter != nil && filter(&entry) {354continue355}356entries = append(entries, entry)357}358if err := iter.Err(); err != nil {359t.Fatal("Cannot iterate over socket enricher map:", err)360}361return entries362}
363
364// bindSocketFn returns a function that creates a socket, binds it and
365// returns the port the socket was bound to.
366func bindSocketFn(ipStr string, domain, typ int, port int) func() (uint16, int, error) {367return func() (uint16, int, error) {368return bindSocket(ipStr, domain, typ, port)369}370}
371
372func bindSocket(ipStr string, domain, typ int, port int) (uint16, int, error) {373return bindSocketWithOpts(ipStr, domain, typ, port, nil)374}
375
376func setProcessName(name string) error {377bytes := append([]byte(name), 0)378return unix.Prctl(unix.PR_SET_NAME, uintptr(unsafe.Pointer(&bytes[0])), 0, 0, 0)379}
380
381func bindSocketWithOpts(ipStr string, domain, typ int, port int, opts []sockOpt) (uint16, int, error) {382// The process name is usually based on the package name383// ("socketenricher.") but it could be changed (e.g. running tests in the384// Goland IDE environment). Make sure the tests work regardless of the385// environment.386//387// Example how to test this:388//389// $ go test -c ./pkg/socketenricher/...390// $ sudo ./socketenricher.test391// PASS392// $ mv socketenricher.test se.test393// $ sudo ./se.test394// FAIL395err := setProcessName("socketenricher.")396if err != nil {397return 0, -1, fmt.Errorf("setProcessName: %w", err)398}399
400fd, err := unix.Socket(domain, typ, 0)401if err != nil {402return 0, -1, err403}404
405for _, opt := range opts {406if err := unix.SetsockoptInt(fd, opt.level, opt.opt, opt.value); err != nil {407return 0, -1, fmt.Errorf("SetsockoptInt: %w", err)408}409}410
411var sa unix.Sockaddr412
413ip := net.ParseIP(ipStr)414
415if ip.To4() != nil {416sa4 := &unix.SockaddrInet4{Port: port}417copy(sa4.Addr[:], ip.To4())418sa = sa4419} else if ip.To16() != nil {420sa6 := &unix.SockaddrInet6{Port: port}421copy(sa6.Addr[:], ip.To16())422sa = sa6423} else {424return 0, -1, fmt.Errorf("invalid IP address")425}426
427if err := unix.Bind(fd, sa); err != nil {428return 0, -1, fmt.Errorf("Bind: %w", err)429}430
431sa2, err := unix.Getsockname(fd)432if err != nil {433return 0, fd, fmt.Errorf("Getsockname: %w", err)434}435
436if ip.To4() != nil {437return uint16(sa2.(*unix.SockaddrInet4).Port), fd, nil438} else if ip.To16() != nil {439return uint16(sa2.(*unix.SockaddrInet6).Port), fd, nil440} else {441return 0, fd, fmt.Errorf("invalid IP address")442}443}
444