From 547af68194a588862a6357652ab1d8a7294c0ea7 Mon Sep 17 00:00:00 2001 From: Bryce Kahle Date: Tue, 14 May 2024 18:54:47 -0700 Subject: [PATCH] add program bypass support --- manager.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++ perf_event.go | 12 --------- probe.go | 51 +++++++++++++++++++++++++++------------ 3 files changed, 102 insertions(+), 28 deletions(-) diff --git a/manager.go b/manager.go index 056622d..0a9e634 100644 --- a/manager.go +++ b/manager.go @@ -139,6 +139,8 @@ type Manager struct { netlinkSocketCache *netlinkSocketCache state state stateLock sync.RWMutex + bypassIndexes map[string]uint32 + maxBypassIndex uint32 // Probes - List of probes handled by the manager Probes []*Probe @@ -447,6 +449,8 @@ func (m *Manager) InitWithOptions(elf io.ReaderAt, options Options) error { if m.options.DefaultRingBufferSize == 0 { m.options.DefaultRingBufferSize = os.Getpagesize() * 16 } + // start with 1, so we know if programs even have a valid index set + m.maxBypassIndex = 1 // perform a quick sanity check on the provided probes and maps if err := m.sanityCheck(); err != nil { @@ -522,6 +526,12 @@ func (m *Manager) InitWithOptions(elf io.ReaderAt, options Options) error { } } + var bypassMap *Map + if _, ok := m.collectionSpec.Maps[bypassMapName]; ok { + bypassMap = &Map{Name: bypassMapName} + m.Maps = append(m.Maps, bypassMap) + } + // Match Maps and program specs if err = m.matchSpecs(); err != nil { m.stateLock.Unlock() @@ -530,6 +540,50 @@ func (m *Manager) InitWithOptions(elf io.ReaderAt, options Options) error { // Configure activated probes m.activateProbes() + + // setup bypass indexes and add constant editors + if bypassMap != nil { + m.bypassIndexes = make(map[string]uint32, len(m.collectionSpec.Programs)) + for progName, prog := range m.collectionSpec.Programs { + if prog.Type == ebpf.SocketFilter { + continue + } + m.options.ConstantEditors = append(m.options.ConstantEditors, ConstantEditor{ + Name: "bypass_program_index", + Value: uint64(m.maxBypassIndex), + ProbeIdentificationPairs: []ProbeIdentificationPair{{ + EBPFFuncName: progName, + }}, + }) + m.bypassIndexes[progName] = m.maxBypassIndex + m.maxBypassIndex += 1 + } + + for _, mProbe := range m.Probes { + if idx, ok := m.bypassIndexes[mProbe.GetEBPFFuncName()]; ok { + mProbe.bypassIndex = idx + mProbe.bypassMap = bypassMap + } + } + cpus, err := ebpf.PossibleCPU() + if err != nil { + m.stateLock.Unlock() + return err + } + if len(bypassSlice) == 0 { + bypassSlice = make([]uint32, cpus) + for i := range bypassSlice { + bypassSlice[i] = 1 + } + } + if len(enableSlice) == 0 { + enableSlice = make([]uint32, cpus) + for i := range enableSlice { + enableSlice[i] = 0 + } + } + } + m.state = initialized m.stateLock.Unlock() resetManager := func(m *Manager) { @@ -908,6 +962,19 @@ func (m *Manager) AddHook(UID string, newProbe *Probe) error { newProbe.program = clonedProg newProbe.programSpec = progSpec + var bypassMap *Map + for _, mp := range m.Maps { + if mp.Name == bypassMapName { + bypassMap = mp + break + } + } + bypassIndex, ok := m.bypassIndexes[newProbe.EBPFFuncName] + if ok && bypassMap != nil { + newProbe.bypassIndex = bypassIndex + newProbe.bypassMap = bypassMap + } + // init program if err = newProbe.init(m); err != nil { // clean up diff --git a/perf_event.go b/perf_event.go index 0116f10..3db2e4e 100644 --- a/perf_event.go +++ b/perf_event.go @@ -85,14 +85,6 @@ func (pe *perfEventLink) Close() error { return pe.fd.Close() } -func (pe *perfEventLink) Pause() error { - return ioctlPerfEventDisable(pe.fd) -} - -func (pe *perfEventLink) Resume() error { - return ioctlPerfEventEnable(pe.fd) -} - func attachPerfEvent(pe *perfEventLink, prog *ebpf.Program) error { if err := ioctlPerfEventSetBPF(pe.fd, prog.FD()); err != nil { return fmt.Errorf("set perf event bpf: %w", err) @@ -211,7 +203,3 @@ func ioctlPerfEventSetBPF(perfEventOpenFD *fd, progFD int) error { func ioctlPerfEventEnable(perfEventOpenFD *fd) error { return unix.IoctlSetInt(int(perfEventOpenFD.raw), unix.PERF_EVENT_IOC_ENABLE, 0) } - -func ioctlPerfEventDisable(perfEventOpenFD *fd) error { - return unix.IoctlSetInt(int(perfEventOpenFD.raw), unix.PERF_EVENT_IOC_DISABLE, 0) -} diff --git a/probe.go b/probe.go index 068f283..d701ba9 100644 --- a/probe.go +++ b/probe.go @@ -24,6 +24,11 @@ const ( AttachWithProbeEvents ) +const bypassMapName = "program_bypassed" + +var bypassSlice []uint32 +var enableSlice []uint32 + // Probe - Main eBPF probe wrapper. This structure is used to store the required data to attach a loaded eBPF // program to its hook point. type Probe struct { @@ -45,6 +50,8 @@ type Probe struct { tcFilter netlink.BpfFilter tcClsActQdisc netlink.Qdisc progLink io.Closer + bypassIndex uint32 + bypassMap *Map // lastError - stores the last error that the probe encountered, it is used to surface a more useful error message // when one of the validators (see Options.ActivatedProbes) fails. @@ -583,17 +590,23 @@ func (p *Probe) pause() error { } v, ok := p.progLink.(pauser) - if !ok { - return fmt.Errorf("pause not supported for program type %s", p.programSpec.Type) + if ok { + if err := v.Pause(); err != nil { + p.lastError = err + return fmt.Errorf("error pausing probe %s: %w", p.ProbeIdentificationPair, err) + } + p.state = paused + return nil } - if err := v.Pause(); err != nil { - p.lastError = err - return fmt.Errorf("error pausing probe %s: %w", p.ProbeIdentificationPair, err) + if p.bypassIndex > 0 && p.bypassMap != nil { + if err := p.bypassMap.array.Update(p.bypassIndex, bypassSlice, ebpf.UpdateExist); err != nil { + return err + } + p.state = paused + return nil } - - p.state = paused - return nil + return fmt.Errorf("pause not supported for program %s", p.ProbeIdentificationPair) } func (p *Probe) resume() error { @@ -604,17 +617,23 @@ func (p *Probe) resume() error { } v, ok := p.progLink.(pauser) - if !ok { - return fmt.Errorf("resume not supported for program type %s", p.programSpec.Type) + if ok { + if err := v.Resume(); err != nil { + p.lastError = err + return fmt.Errorf("error resuming probe %s: %w", p.ProbeIdentificationPair, err) + } + p.state = running + return nil } - if err := v.Resume(); err != nil { - p.lastError = err - return fmt.Errorf("error resuming probe %s: %w", p.ProbeIdentificationPair, err) + if p.bypassIndex > 0 && p.bypassMap != nil { + if err := p.bypassMap.array.Update(p.bypassIndex, enableSlice, ebpf.UpdateExist); err != nil { + return err + } + p.state = running + return nil } - - p.state = running - return nil + return fmt.Errorf("resume not supported for program %s", p.ProbeIdentificationPair) } // Detach - Detaches the probe from its hook point depending on the program type and the provided parameters. This