mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-29 19:11:28 +02:00
150 lines
3.7 KiB
Go
150 lines
3.7 KiB
Go
package ebpf
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
|
|
"github.com/cilium/ebpf/link"
|
|
"github.com/cilium/ebpf/rlimit"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/netbirdio/netbird/client/internal/ebpf/manager"
|
|
)
|
|
|
|
const (
|
|
xdpProgName = "nb_xdp_prog"
|
|
|
|
mapKeyFeatures uint32 = 0
|
|
|
|
featureFlagWGProxy = 0b00000001
|
|
featureFlagDnsForwarder = 0b00000010
|
|
)
|
|
|
|
var (
|
|
singleton manager.Manager
|
|
singletonLock = &sync.Mutex{}
|
|
)
|
|
|
|
// required packages libbpf-dev, libc6-dev-i386-amd64-cross
|
|
|
|
// GeneralManager is used to load multiple eBPF programs with a custom check (if then) done in prog.c
|
|
// The manager simply adds a feature (byte) of each program to a map that is shared between the userspace and kernel.
|
|
// When packet arrives, the C code checks for each feature (if it is set) and executes each enabled program (e.g., dns_fwd.c and wg_proxy.c).
|
|
//
|
|
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang-14 bpf src/prog.c -- -I /usr/x86_64-linux-gnu/include
|
|
type GeneralManager struct {
|
|
lock sync.Mutex
|
|
link link.Link
|
|
featureFlags uint16
|
|
bpfObjs bpfObjects
|
|
}
|
|
|
|
// GetEbpfManagerInstance return a static eBpf Manager instance
|
|
func GetEbpfManagerInstance() manager.Manager {
|
|
singletonLock.Lock()
|
|
defer singletonLock.Unlock()
|
|
if singleton != nil {
|
|
return singleton
|
|
}
|
|
singleton = &GeneralManager{}
|
|
return singleton
|
|
}
|
|
|
|
func (tf *GeneralManager) setFeatureFlag(feature uint16) {
|
|
tf.featureFlags |= feature
|
|
}
|
|
|
|
func (tf *GeneralManager) loadXdp() error {
|
|
if tf.link != nil {
|
|
return nil
|
|
}
|
|
// it required for Docker
|
|
err := rlimit.RemoveMemlock()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
iFace, err := net.InterfaceByName("lo")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// lo has no native XDP, so the program runs in generic mode. Unless it
|
|
// declares multi-buffer support the kernel must linearize every non-linear
|
|
// skb before running it. Loopback packets are up to 64 KB, so that is a
|
|
// contiguous GFP_ATOMIC allocation per packet, and when it fails the packet
|
|
// is dropped before the program runs, stalling local TCP connections.
|
|
// Multi-buffer XDP in generic mode requires kernel 6.3, so fall back to a
|
|
// plain attach when the kernel rejects it.
|
|
err = tf.attachXdp(iFace.Index, true)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
log.Debugf("failed to attach multi-buffer xdp program, retrying without it: %s", err)
|
|
|
|
return tf.attachXdp(iFace.Index, false)
|
|
}
|
|
|
|
func (tf *GeneralManager) attachXdp(iFaceIndex int, multiBuffer bool) error {
|
|
spec, err := loadBpf()
|
|
if err != nil {
|
|
return fmt.Errorf("load bpf spec: %w", err)
|
|
}
|
|
|
|
if multiBuffer {
|
|
prog, ok := spec.Programs[xdpProgName]
|
|
if !ok {
|
|
return fmt.Errorf("program %s not found in bpf spec", xdpProgName)
|
|
}
|
|
prog.Flags |= unix.BPF_F_XDP_HAS_FRAGS
|
|
}
|
|
|
|
if err := spec.LoadAndAssign(&tf.bpfObjs, nil); err != nil {
|
|
return fmt.Errorf("load bpf objects: %w", err)
|
|
}
|
|
|
|
tf.link, err = link.AttachXDP(link.XDPOptions{
|
|
Program: tf.bpfObjs.NbXdpProg,
|
|
Interface: iFaceIndex,
|
|
})
|
|
if err != nil {
|
|
if closeErr := tf.bpfObjs.Close(); closeErr != nil {
|
|
log.Debugf("failed to close bpf objects after xdp attach error: %s", closeErr)
|
|
}
|
|
tf.link = nil
|
|
return fmt.Errorf("attach xdp: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (tf *GeneralManager) unsetFeatureFlag(feature uint16) error {
|
|
tf.lock.Lock()
|
|
defer tf.lock.Unlock()
|
|
tf.featureFlags &^= feature
|
|
|
|
if tf.link == nil {
|
|
return nil
|
|
}
|
|
|
|
if tf.featureFlags == 0 {
|
|
return tf.close()
|
|
}
|
|
|
|
return tf.bpfObjs.NbFeatures.Put(mapKeyFeatures, tf.featureFlags)
|
|
}
|
|
|
|
func (tf *GeneralManager) close() error {
|
|
log.Debugf("detach ebpf program ")
|
|
err := tf.bpfObjs.Close()
|
|
if err != nil {
|
|
log.Warnf("failed to close eBpf objects: %s", err)
|
|
}
|
|
|
|
err = tf.link.Close()
|
|
tf.link = nil
|
|
return err
|
|
}
|