Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/linux-port.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: linux-port

on:
pull_request:
branches:
- linux-port
workflow_dispatch:

concurrency:
group: linux-port-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
GO_VERSION: 1.26.x

jobs:
ebpf:
runs-on: ubuntu-latest
steps:
- name: Validate PR title
if: github.event_name == 'pull_request'
uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Install clang
run: |
sudo apt-get update
sudo apt-get install -y clang llvm
clang --version
- name: Check generation drift
run: |
chmod +x internal/ebpf/generate.sh
./internal/ebpf/generate.sh
git diff --exit-code -- internal/ebpf
- name: Unit tests
run: go test ./internal/ebpf ./internal/bootstrap ./pkg/event ./pkg/ps ./pkg/api ./pkg/util/signals
- name: Privileged process source
run: sudo -E env "PATH=$PATH" go test -tags ebpf_integration -count=1 ./internal/ebpf
178 changes: 163 additions & 15 deletions internal/bootstrap/bootstrap_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,46 +22,194 @@
package bootstrap

import (
"context"
"errors"
"fmt"
"net"
"os"

"github.com/rabbitstack/fibratus/pkg/aggregator"
"github.com/rabbitstack/fibratus/pkg/alertsender"
"github.com/rabbitstack/fibratus/pkg/api"
"github.com/rabbitstack/fibratus/pkg/config"
"github.com/rabbitstack/fibratus/pkg/filter"
"github.com/rabbitstack/fibratus/pkg/ps"
"github.com/rabbitstack/fibratus/pkg/rules"
"github.com/rabbitstack/fibratus/pkg/util/multierror"
"github.com/rabbitstack/fibratus/pkg/util/signals"
"github.com/rabbitstack/fibratus/pkg/util/version"
log "github.com/sirupsen/logrus"
)

var ErrCaptureNotWired = errors.New("linux event capture is not wired yet")
// instanceSocket is an abstract UNIX domain socket name. Binding it acts as
// a kernel-wide mutex that is released automatically when the process
// terminates, so no filesystem cleanup is required.
const instanceSocket = "@fibratus"

// ErrAlreadyRunning signals a Fibratus process is already running.
var ErrAlreadyRunning = errors.New("an instance of Fibratus process is already running in the system")

// App centralizes the core building blocks responsible
// for event acquisition, rule engine initialization,
// and event routing to the output sinks.
type App struct {
config *config.Config
evs *EventSourceControl
config *config.Config
evs *EventSourceControl
engine *rules.Engine
psnap ps.Snapshotter
agg *aggregator.BufferedAggregator
signals chan struct{}
instance net.Listener
}

// Option enables changing the behaviour of the bootstrap application.
type Option func(*opts)

type opts struct{}

func WithSignals() Option {
return func(*opts) {}
type opts struct {
installSignals bool
}

func WithDebugPrivilege() Option {
return func(*opts) {}
// WithSignals installs signal handlers.
func WithSignals() Option {
return func(o *opts) {
o.installSignals = true
}
}

// NewApp constructs a new bootstrap application with the specified configuration
// and a list of options.
func NewApp(cfg *config.Config, options ...Option) (*App, error) {
if err := InitConfigAndLogger(cfg); err != nil {
return nil, err
}
return &App{config: cfg, evs: NewEventSourceControl(cfg)}, nil
var o opts
var sigs chan struct{}
for _, opt := range options {
opt(&o)
}
if o.installSignals {
sigs = signals.Install()
}

psnap := ps.NewSnapshotter()

var engine *rules.Engine
var rs *config.RulesCompileResult
if cfg.Filters != nil && cfg.Filters.Rules.Enabled && !cfg.ForwardMode && !cfg.IsCaptureSet() && !cfg.IsFilamentSet() {
engine = rules.NewEngine(psnap, cfg)
var err error
rs, err = engine.Compile()
if err != nil {
return nil, err
}
if rs != nil {
log.Infof("rules compile summary: %s", rs)
}
} else {
log.Info("rule engine is disabled")
}

return &App{
config: cfg,
evs: NewEventSourceControl(psnap, cfg, rs),
engine: engine,
psnap: psnap,
signals: sigs,
}, nil
}

func (f *App) Run([]string) error {
if err := f.evs.Open(f.config); err != nil {
// Run configures and opens the event source to start consuming events.
func (f *App) Run(args []string) error {
if f.evs == nil {
panic("event source is nil")
}
cfg := f.config

if cfg.IsFilamentSet() {
return fmt.Errorf("filaments are not supported on Linux")
}

if !f.isSingleInstance() {
return ErrAlreadyRunning
}

log.Infof("bootstrapping with pid %d. Version: %s", os.Getpid(), version.Get())
log.Infof("configuration options: %s", cfg.Print())

fltr, err := filter.NewFromCLI(args, cfg)
if err != nil {
return err
}
return ErrCaptureNotWired
if fltr != nil {
f.evs.SetFilter(fltr)
}
if f.engine != nil {
f.evs.RegisterEventListener(f.engine)
}

if err := f.evs.Open(cfg); err != nil {
return multierror.Wrap(err, f.evs.Close())
}

f.agg, err = aggregator.NewBuffered(
f.evs.Events(),
f.evs.Errors(),
cfg.Aggregator,
cfg.Output,
cfg.Transformers,
cfg.Alertsenders,
)
if err != nil {
return err
}
return api.StartServer(cfg)
}

func (*App) Wait() {}
// Wait waits for the app to receive the termination signal.
func (f *App) Wait() {
if f.signals != nil {
<-f.signals
}
}

// Shutdown is responsible for tearing down everything gracefully.
func (f *App) Shutdown() error {
return f.evs.Close()
errs := make([]error, 0)
if f.evs != nil {
if err := f.evs.Close(); err != nil {
errs = append(errs, err)
}
}
if f.psnap != nil {
if err := f.psnap.Close(); err != nil {
errs = append(errs, err)
}
}
if f.agg != nil {
if err := f.agg.Stop(); err != nil {
errs = append(errs, err)
}
}
if err := api.CloseServer(); err != nil {
errs = append(errs, err)
}
if err := alertsender.ShutdownAll(); err != nil {
errs = append(errs, err)
}
if f.instance != nil {
_ = f.instance.Close()
}
return multierror.Wrap(errs...)
}

// isSingleInstance checks if there is already an instance of Fibratus
// running in the system.
func (f *App) isSingleInstance() bool {
var lc net.ListenConfig
l, err := lc.Listen(context.Background(), "unix", instanceSocket)
if err != nil {
return false
}
f.instance = l
return true
}
32 changes: 25 additions & 7 deletions internal/bootstrap/source_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,25 @@
package bootstrap

import (
libebpf "github.com/rabbitstack/fibratus/internal/ebpf"
"github.com/rabbitstack/fibratus/pkg/config"
"github.com/rabbitstack/fibratus/pkg/event"
"github.com/rabbitstack/fibratus/pkg/filter"
"github.com/rabbitstack/fibratus/pkg/ps"
"github.com/rabbitstack/fibratus/pkg/source"
)

// EventSourceControl abstracts away the management of event sources.
type EventSourceControl struct {
evs *stubEventSource
evs source.EventSource
}

func NewEventSourceControl(*config.Config) *EventSourceControl {
return &EventSourceControl{evs: &stubEventSource{}}
func NewEventSourceControl(
psnap ps.Snapshotter,
cfg *config.Config,
compiler *config.RulesCompileResult,
) *EventSourceControl {
return &EventSourceControl{evs: libebpf.NewEventSource(psnap, cfg, compiler)}
}

func (s *EventSourceControl) Open(cfg *config.Config) error {
Expand All @@ -41,10 +51,18 @@ func (s *EventSourceControl) Close() error {
return s.evs.Close()
}

type stubEventSource struct{}
func (s *EventSourceControl) Errors() <-chan error {
return s.evs.Errors()
}

func (s *EventSourceControl) Events() <-chan *event.Event {
return s.evs.Events()
}

func (*stubEventSource) Open(*config.Config) error {
return nil
func (s *EventSourceControl) SetFilter(f filter.Filter) {
s.evs.SetFilter(f)
}

func (*stubEventSource) Close() error { return nil }
func (s *EventSourceControl) RegisterEventListener(lis event.Listener) {
s.evs.RegisterEventListener(lis)
}
33 changes: 33 additions & 0 deletions internal/ebpf/btf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:build linux

/*
* Copyright 2026 by Mostafa Moradian
* https://www.fibratus.io
* All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package ebpf

import (
"fmt"
"runtime"
)

func checkRuntimeSupport() (*PrerequisiteReport, error) {
if runtime.GOARCH != "amd64" {
return nil, fmt.Errorf("linux eBPF capture requires amd64, got %s", runtime.GOARCH)
}
return ProbePrerequisites()
}
Loading
Loading