add runtime code

This commit is contained in:
2025-04-15 19:59:12 +08:00
parent 0a5f457059
commit 51aa9cb67f
34 changed files with 1941 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
#include <bpf/bpf_endian.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "sockmap.h"
#include "target_ip_map.h"
+76
View File
@@ -0,0 +1,76 @@
#include "common.h"
enum filter_ret_code {
// 不处理当前连接
PASS = 0,
// 处理当前连接
STORE = 1,
};
char LICENSE[] SEC("license") = "Dual BSD/GPL";
static inline
void store_sock_ops(struct bpf_sock_ops *skops)
{
struct sock_key key = {
.dip = skops->remote_ip4,
.sip = skops->local_ip4,
.sport = bpf_htonl(skops->local_port), /* convert to network byte order */
.dport = skops->remote_port,
.family = skops->family,
};
int ret = bpf_sock_hash_update(skops, &sock_ops_map, &key, BPF_NOEXIST);
if (ret != 0) {
bpf_printk("sock_hash_update() failed, ret: %d\n", ret);
}
}
// 判断传入连接是否需要处理
static inline
__u8 sock_filter(struct bpf_sock_ops *skops)
{
if (skops->family != 2){ // AF_INET
return PASS;
}
/*
local traffic only, so check both local and remote IPs
*/
__u32 remote_ip = skops->remote_ip4;
__u32 local_ip = skops->local_ip4;
// 检查 remote_ip 是否在目标 map 中
if (!bpf_map_lookup_elem(&target_ip_map, &remote_ip)) {
return PASS;
}
// 如果是本地流量,大概率remote ip 和 local ip 是相同的,此时可以省去一次 map 查询
if (remote_ip == local_ip) {
return STORE;
}
// 检查 local_ip 是否在目标 map 中
if (!bpf_map_lookup_elem(&target_ip_map, &local_ip)) {
return PASS;
}
return STORE;
}
SEC("sockops")
int bpf_sockops_handler(struct bpf_sock_ops *skops)
{
// only interested in established events
if (skops->op != BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB &&
skops->op != BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB) {
return BPF_OK;
}
if (sock_filter(skops)){ // AF_INET
store_sock_ops(skops); // 将 socket 信息记录到到 sockmap
}
return BPF_OK;
}
+29
View File
@@ -0,0 +1,29 @@
#include "common.h"
char LICENSE[] SEC("license") = "Dual BSD/GPL";
SEC("sk_msg")
int bpf_redir(struct sk_msg_md *msg)
{
struct sock_key key = {
.sip = msg->remote_ip4,
.dip = msg->local_ip4,
.dport = bpf_htonl(msg->local_port), /* convert to network byte order */
.sport = msg->remote_port,
.family = msg->family,
};
int ret = bpf_msg_redirect_hash(msg, &sock_ops_map, &key, BPF_F_INGRESS);
if (ret == SK_DROP) {
bpf_printk("redirect from %d.%d.%d.%d:%d to %d.%d.%d.%d:%d failed\n",
key.sip & 0xff, (key.sip >> 8) & 0xff,
(key.sip >> 16) & 0xff, (key.sip >> 24) & 0xff,
bpf_ntohl(key.sport),
key.dip & 0xff, (key.dip >> 8) & 0xff,
(key.dip >> 16) & 0xff, (key.dip >> 24) & 0xff,
bpf_ntohl(key.dport));
}
// 无论是否重定向成功,都返回 SK_PASS。这样失败的重定向还可以使用其他方法处理
return SK_PASS;
}
+17
View File
@@ -0,0 +1,17 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
struct sock_key {
__u32 sip;
__u32 dip;
__u32 sport;
__u32 dport;
__u32 family;
};
struct {
__uint(type, BPF_MAP_TYPE_SOCKHASH);
__uint(max_entries, 65535);
__type(key, struct sock_key);
__type(value, int);
} sock_ops_map SEC(".maps");
+10
View File
@@ -0,0 +1,10 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
// 存储需要contrack的IP地址
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, __u32); // key 类型(目标 IP 地址)
__type(value, __u8); // value 类型(标志位,无实际用途,设置为 1 表示存在)
__uint(max_entries, 1024); // 最多存储 1024 个目标 IP
} target_ip_map SEC(".maps");
BIN
View File
Binary file not shown.
+53
View File
@@ -0,0 +1,53 @@
package ebpf
const DefaultInterfaceName string = "neto"
// Ebpf should be initialized only once. when agent.reloads,
// agent.try to start ebpf again. so we need to keep track of
// how many instances of ebpf are running. only when InstanceRefCounter
// is 1, we should start ebpf. when InstanceRefCounter is 0, we should stop ebpf.
var InstanceRefCounter uint32 = 0
var ebpf *Ebpf
type App struct {
InterfaceName []string `json:"interface_name,omitempty"`
}
func NewApp() *App {
return &App{}
}
func (a *App) Provision() error {
if len(a.InterfaceName) == 0 {
a.InterfaceName = []string{DefaultInterfaceName}
}
InstanceRefCounter++
if InstanceRefCounter == 1 {
var err error
ebpf, err = ProvisionEBPF()
if err != nil {
return err
}
ebpf.SetIfName(a.InterfaceName)
ebpf.Start()
}
ebpf.SetIfName(a.InterfaceName)
return nil
}
func (a *App) Start() error {
return nil
}
func (a *App) Stop() error {
InstanceRefCounter--
if InstanceRefCounter == 0 {
ebpf.Stop()
}
return nil
}
+6
View File
@@ -0,0 +1,6 @@
#include <bpf/bpf_endian.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "sockmap.h"
#include "target_ip_map.h"
+76
View File
@@ -0,0 +1,76 @@
#include "common.h"
enum filter_ret_code {
// 不处理当前连接
PASS = 0,
// 处理当前连接
STORE = 1,
};
char LICENSE[] SEC("license") = "Dual BSD/GPL";
static inline
void store_sock_ops(struct bpf_sock_ops *skops)
{
struct sock_key key = {
.dip = skops->remote_ip4,
.sip = skops->local_ip4,
.sport = bpf_htonl(skops->local_port), /* convert to network byte order */
.dport = skops->remote_port,
.family = skops->family,
};
int ret = bpf_sock_hash_update(skops, &sock_ops_map, &key, BPF_NOEXIST);
if (ret != 0) {
bpf_printk("sock_hash_update() failed, ret: %d\n", ret);
}
}
// 判断传入连接是否需要处理
static inline
__u8 sock_filter(struct bpf_sock_ops *skops)
{
if (skops->family != 2){ // AF_INET
return PASS;
}
/*
local traffic only, so check both local and remote IPs
*/
__u32 remote_ip = skops->remote_ip4;
__u32 local_ip = skops->local_ip4;
// 检查 remote_ip 是否在目标 map 中
if (!bpf_map_lookup_elem(&target_ip_map, &remote_ip)) {
return PASS;
}
// 如果是本地流量,大概率remote ip 和 local ip 是相同的,此时可以省去一次 map 查询
if (remote_ip == local_ip) {
return STORE;
}
// 检查 local_ip 是否在目标 map 中
if (!bpf_map_lookup_elem(&target_ip_map, &local_ip)) {
return PASS;
}
return STORE;
}
SEC("sockops")
int bpf_sockops_handler(struct bpf_sock_ops *skops)
{
// only interested in established events
if (skops->op != BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB &&
skops->op != BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB) {
return BPF_OK;
}
if (sock_filter(skops)){ // AF_INET
store_sock_ops(skops); // 将 socket 信息记录到到 sockmap
}
return BPF_OK;
}
+29
View File
@@ -0,0 +1,29 @@
#include "common.h"
char LICENSE[] SEC("license") = "Dual BSD/GPL";
SEC("sk_msg")
int bpf_redir(struct sk_msg_md *msg)
{
struct sock_key key = {
.sip = msg->remote_ip4,
.dip = msg->local_ip4,
.dport = bpf_htonl(msg->local_port), /* convert to network byte order */
.sport = msg->remote_port,
.family = msg->family,
};
int ret = bpf_msg_redirect_hash(msg, &sock_ops_map, &key, BPF_F_INGRESS);
if (ret == SK_DROP) {
bpf_printk("redirect from %d.%d.%d.%d:%d to %d.%d.%d.%d:%d failed\n",
key.sip & 0xff, (key.sip >> 8) & 0xff,
(key.sip >> 16) & 0xff, (key.sip >> 24) & 0xff,
bpf_ntohl(key.sport),
key.dip & 0xff, (key.dip >> 8) & 0xff,
(key.dip >> 16) & 0xff, (key.dip >> 24) & 0xff,
bpf_ntohl(key.dport));
}
// 无论是否重定向成功,都返回 SK_PASS。这样失败的重定向还可以使用其他方法处理
return SK_PASS;
}
+17
View File
@@ -0,0 +1,17 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
struct sock_key {
__u32 sip;
__u32 dip;
__u32 sport;
__u32 dport;
__u32 family;
};
struct {
__uint(type, BPF_MAP_TYPE_SOCKHASH);
__uint(max_entries, 65535);
__type(key, struct sock_key);
__type(value, int);
} sock_ops_map SEC(".maps");
+10
View File
@@ -0,0 +1,10 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
// 存储需要contrack的IP地址
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, __u32); // key 类型(目标 IP 地址)
__type(value, __u8); // value 类型(标志位,无实际用途,设置为 1 表示存在)
__uint(max_entries, 1024); // 最多存储 1024 个目标 IP
} target_ip_map SEC(".maps");
+82
View File
@@ -0,0 +1,82 @@
package contrack
import (
"fmt"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
)
type Contrack struct {
obj contrackObjects
cg link.Link
targetIPs map[uint32]struct{}
}
func New() *Contrack {
return &Contrack{
obj: contrackObjects{},
targetIPs: make(map[uint32]struct{}),
}
}
func (c *Contrack) Start() error {
if err := loadContrackObjects(&c.obj, nil); err != nil {
return fmt.Errorf("loading contrack objects failed: %w", err)
}
cgroupPath, err := detectCgroupPath()
if err != nil {
return fmt.Errorf("attaching contrack cgroup failed for detecting cgroup path: %w", err)
}
c.cg, err = link.AttachCgroup(link.CgroupOptions{
Path: cgroupPath,
Attach: ebpf.AttachCGroupSockOps,
Program: c.obj.BpfSockopsHandler,
})
if err != nil {
return fmt.Errorf("attaching contrack cgroup failed: %w", err)
}
return nil
}
func (c *Contrack) Close() {
if c.cg != nil {
c.cg.Close()
}
c.obj.Close()
}
func (c *Contrack) SockOpsMap() *ebpf.Map {
return c.obj.SockOpsMap
}
// AddTargetIp adds an IP to the target_ip_map
func (c *Contrack) AddTargetIP(ip *IP) error {
value := uint8(1) // Value is not used, just needs to exist
if err := c.obj.TargetIpMap.Put(ip.Key(), value); err != nil {
return fmt.Errorf("failed to update target_ip_map with IP %s: %v", ip, err)
}
c.targetIPs[ip.Key()] = struct{}{}
return nil
}
// RemoveTargetIPWithPreCheck removes an IP from the target_ip_map after checking if it exists
func (c *Contrack) RemoveTargetIPWithPreCheck(ip *IP) error {
if _, ok := c.targetIPs[ip.Key()]; !ok {
return fmt.Errorf("IP %s is not in target_ip_map", ip)
}
return c.RemoveTargetIP(ip)
}
// RemoveTargetIP removes an IP from the target_ip_map
func (c *Contrack) RemoveTargetIP(ip *IP) error {
if err := c.obj.TargetIpMap.Delete(ip.Key()); err != nil {
return fmt.Errorf("failed to remove IP %s from target_ip_map: %v", ip, err)
}
delete(c.targetIPs, ip.Key())
return nil
}
+144
View File
@@ -0,0 +1,144 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build mips || mips64 || ppc64 || s390x
package contrack
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type contrackSockKey struct {
Sip uint32
Dip uint32
Sport uint32
Dport uint32
Family uint32
}
// loadContrack returns the embedded CollectionSpec for contrack.
func loadContrack() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_ContrackBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load contrack: %w", err)
}
return spec, err
}
// loadContrackObjects loads contrack and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *contrackObjects
// *contrackPrograms
// *contrackMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadContrackObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadContrack()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// contrackSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type contrackSpecs struct {
contrackProgramSpecs
contrackMapSpecs
contrackVariableSpecs
}
// contrackProgramSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type contrackProgramSpecs struct {
BpfSockopsHandler *ebpf.ProgramSpec `ebpf:"bpf_sockops_handler"`
}
// contrackMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type contrackMapSpecs struct {
SockOpsMap *ebpf.MapSpec `ebpf:"sock_ops_map"`
TargetIpMap *ebpf.MapSpec `ebpf:"target_ip_map"`
}
// contrackVariableSpecs contains global variables before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type contrackVariableSpecs struct {
}
// contrackObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
type contrackObjects struct {
contrackPrograms
contrackMaps
contrackVariables
}
func (o *contrackObjects) Close() error {
return _ContrackClose(
&o.contrackPrograms,
&o.contrackMaps,
)
}
// contrackMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
type contrackMaps struct {
SockOpsMap *ebpf.Map `ebpf:"sock_ops_map"`
TargetIpMap *ebpf.Map `ebpf:"target_ip_map"`
}
func (m *contrackMaps) Close() error {
return _ContrackClose(
m.SockOpsMap,
m.TargetIpMap,
)
}
// contrackVariables contains all global variables after they have been loaded into the kernel.
//
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
type contrackVariables struct {
}
// contrackPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
type contrackPrograms struct {
BpfSockopsHandler *ebpf.Program `ebpf:"bpf_sockops_handler"`
}
func (p *contrackPrograms) Close() error {
return _ContrackClose(
p.BpfSockopsHandler,
)
}
func _ContrackClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//
//go:embed contrack_bpfeb.o
var _ContrackBytes []byte
+144
View File
@@ -0,0 +1,144 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64
package contrack
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type contrackSockKey struct {
Sip uint32
Dip uint32
Sport uint32
Dport uint32
Family uint32
}
// loadContrack returns the embedded CollectionSpec for contrack.
func loadContrack() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_ContrackBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load contrack: %w", err)
}
return spec, err
}
// loadContrackObjects loads contrack and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *contrackObjects
// *contrackPrograms
// *contrackMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadContrackObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadContrack()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// contrackSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type contrackSpecs struct {
contrackProgramSpecs
contrackMapSpecs
contrackVariableSpecs
}
// contrackProgramSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type contrackProgramSpecs struct {
BpfSockopsHandler *ebpf.ProgramSpec `ebpf:"bpf_sockops_handler"`
}
// contrackMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type contrackMapSpecs struct {
SockOpsMap *ebpf.MapSpec `ebpf:"sock_ops_map"`
TargetIpMap *ebpf.MapSpec `ebpf:"target_ip_map"`
}
// contrackVariableSpecs contains global variables before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type contrackVariableSpecs struct {
}
// contrackObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
type contrackObjects struct {
contrackPrograms
contrackMaps
contrackVariables
}
func (o *contrackObjects) Close() error {
return _ContrackClose(
&o.contrackPrograms,
&o.contrackMaps,
)
}
// contrackMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
type contrackMaps struct {
SockOpsMap *ebpf.Map `ebpf:"sock_ops_map"`
TargetIpMap *ebpf.Map `ebpf:"target_ip_map"`
}
func (m *contrackMaps) Close() error {
return _ContrackClose(
m.SockOpsMap,
m.TargetIpMap,
)
}
// contrackVariables contains all global variables after they have been loaded into the kernel.
//
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
type contrackVariables struct {
}
// contrackPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadContrackObjects or ebpf.CollectionSpec.LoadAndAssign.
type contrackPrograms struct {
BpfSockopsHandler *ebpf.Program `ebpf:"bpf_sockops_handler"`
}
func (p *contrackPrograms) Close() error {
return _ContrackClose(
p.BpfSockopsHandler,
)
}
func _ContrackClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//
//go:embed contrack_bpfel.o
var _ContrackBytes []byte
+3
View File
@@ -0,0 +1,3 @@
package contrack
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go contrack ../bpf/contrack.bpf.c
+41
View File
@@ -0,0 +1,41 @@
package contrack
import (
"bytes"
"encoding/binary"
"net"
)
type IP struct {
net.IP
key uint32
}
func (ip IP) Key() uint32 {
return ip.key
}
func ParseIPStr(ipStr string) *IP {
ip := net.ParseIP(ipStr)
if ip == nil {
return nil
}
return ParseIP(ip)
}
func ParseIP(ip net.IP) *IP {
parsedIP := ip.To4()
if parsedIP == nil {
return nil
}
var key uint32
buf := bytes.NewReader(parsedIP)
binary.Read(buf, binary.LittleEndian, &key)
return &IP{
IP: parsedIP,
key: key,
}
}
+29
View File
@@ -0,0 +1,29 @@
package contrack
import (
"bufio"
"errors"
"os"
"strings"
)
// detectCgroupPath returns the first-found mount point of type cgroup2
// and stores it in the cgroupPath global variable.
func detectCgroupPath() (string, error) {
f, err := os.Open("/proc/mounts")
if err != nil {
return "", err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
// example fields: cgroup2 /sys/fs/cgroup/unified cgroup2 rw,nosuid,nodev,noexec,relatime 0 0
fields := strings.Split(scanner.Text(), " ")
if len(fields) >= 3 && fields[2] == "cgroup2" {
return fields[1], nil
}
}
return "", errors.New("cgroup2 not mounted")
}
+118
View File
@@ -0,0 +1,118 @@
package ebpf
import (
"context"
"errors"
"floares/lib/ebpf/ebpf/contrack"
"floares/lib/ebpf/ebpf/redirect"
"fmt"
"slices"
"github.com/cilium/ebpf/rlimit"
"github.com/vishvananda/netlink"
"go.uber.org/zap"
)
type Ebpf struct {
contrack *contrack.Contrack
redirect *redirect.Redirect
cancel context.CancelFunc
ifName []string
logger *zap.Logger
}
func (e *Ebpf) Start() {
var ctx context.Context
ctx, e.cancel = context.WithCancel(context.Background())
addrUpdate := make(chan netlink.AddrUpdate)
netlink.AddrSubscribe(addrUpdate, ctx.Done())
go func() {
for {
select {
case addr := <-addrUpdate:
if filterByIfName(addr, e.ifName) {
fmt.Println(addr.LinkIndex, addr.LinkAddress)
parsedIP := contrack.ParseIP(addr.LinkAddress.IP)
if parsedIP == nil {
e.logger.Debug("support only ipv4 now, not: ", zap.String("ip", addr.LinkAddress.IP.String()))
continue
}
if addr.NewAddr {
if err := e.contrack.AddTargetIP(parsedIP); err != nil {
e.logger.Error("failed to add target ip", zap.String("ip", parsedIP.String()), zap.Error(err))
} else {
e.logger.Debug("added target ip", zap.String("ip", parsedIP.String()))
}
} else {
if err := e.contrack.RemoveTargetIPWithPreCheck(parsedIP); err != nil {
e.logger.Error("failed to remove target ip", zap.String("ip", parsedIP.String()), zap.Error(err))
} else {
e.logger.Debug("removed target ip", zap.String("ip", parsedIP.String()))
}
}
}
case <-ctx.Done():
return
}
}
}()
}
func (e *Ebpf) Stop() {
e.cancel()
}
func (e *Ebpf) SetIfName(ifName []string) {
e.ifName = ifName
}
func (e *Ebpf) SetLogger(logger *zap.Logger) {
e.logger = logger
}
// ProvisionEBPF caller must call this function only once.
func ProvisionEBPF() (*Ebpf, error) {
// Remove resource limits for kernels <5.11.
if err := rlimit.RemoveMemlock(); err != nil {
return nil, fmt.Errorf("removing memlock: %w", err)
}
con := contrack.New()
if err := con.Start(); err != nil {
return nil, fmt.Errorf("starting contrack failed: %w", err)
}
red := redirect.New(con.SockOpsMap())
if err := red.Start(); err != nil {
return nil, fmt.Errorf("starting redirect failed: %w", err)
}
return &Ebpf{
contrack: con,
redirect: red,
}, nil
}
// filterByIfName filters the AddrUpdate by interface name.
// When AddrUpdate.LinkIndex is not found there is two cases, we handle it in different ways:
// 1. The addr is added. returns false.
// 2. The addr is removed, return true. Because the link is deleted first and then the addr is removed.
func filterByIfName(au netlink.AddrUpdate, ifName []string) bool {
link, err := netlink.LinkByIndex(au.LinkIndex)
if err != nil {
if errors.As(err, &netlink.LinkNotFoundError{}) && !au.NewAddr {
return true
}
return false
}
if slices.Contains(ifName, link.Attrs().Name) {
return true
}
return false
}
+3
View File
@@ -0,0 +1,3 @@
package redirect
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go redirect ../bpf/redirect.bpf.c
+47
View File
@@ -0,0 +1,47 @@
package redirect
import (
"fmt"
"github.com/cilium/ebpf"
)
type Redirect struct {
sockMap *ebpf.Map
obj redirectObjects
}
func New(sockMap *ebpf.Map) *Redirect {
return &Redirect{
sockMap: sockMap,
obj: redirectObjects{},
}
}
func (s *Redirect) Start() (err error) {
if err = s.loadProgram(); err != nil {
return fmt.Errorf("loading redirect objects failed: %w", err)
}
err = AttachSkMsg(SkMsgOptions{
Program: s.obj.BpfRedir,
Map: s.sockMap.FD(),
})
if err != nil {
return fmt.Errorf("attaching redirect failed: %w", err)
}
return nil
}
func (s *Redirect) Close() {
s.obj.Close()
}
func (s *Redirect) loadProgram() error {
return loadRedirectObjects(&s.obj, &ebpf.CollectionOptions{
MapReplacements: map[string]*ebpf.Map{
"sock_ops_map": s.sockMap,
},
})
}
+144
View File
@@ -0,0 +1,144 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build mips || mips64 || ppc64 || s390x
package redirect
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type redirectSockKey struct {
Sip uint32
Dip uint32
Sport uint32
Dport uint32
Family uint32
}
// loadRedirect returns the embedded CollectionSpec for redirect.
func loadRedirect() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_RedirectBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load redirect: %w", err)
}
return spec, err
}
// loadRedirectObjects loads redirect and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *redirectObjects
// *redirectPrograms
// *redirectMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadRedirectObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadRedirect()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// redirectSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type redirectSpecs struct {
redirectProgramSpecs
redirectMapSpecs
redirectVariableSpecs
}
// redirectProgramSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type redirectProgramSpecs struct {
BpfRedir *ebpf.ProgramSpec `ebpf:"bpf_redir"`
}
// redirectMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type redirectMapSpecs struct {
SockOpsMap *ebpf.MapSpec `ebpf:"sock_ops_map"`
TargetIpMap *ebpf.MapSpec `ebpf:"target_ip_map"`
}
// redirectVariableSpecs contains global variables before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type redirectVariableSpecs struct {
}
// redirectObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
type redirectObjects struct {
redirectPrograms
redirectMaps
redirectVariables
}
func (o *redirectObjects) Close() error {
return _RedirectClose(
&o.redirectPrograms,
&o.redirectMaps,
)
}
// redirectMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
type redirectMaps struct {
SockOpsMap *ebpf.Map `ebpf:"sock_ops_map"`
TargetIpMap *ebpf.Map `ebpf:"target_ip_map"`
}
func (m *redirectMaps) Close() error {
return _RedirectClose(
m.SockOpsMap,
m.TargetIpMap,
)
}
// redirectVariables contains all global variables after they have been loaded into the kernel.
//
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
type redirectVariables struct {
}
// redirectPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
type redirectPrograms struct {
BpfRedir *ebpf.Program `ebpf:"bpf_redir"`
}
func (p *redirectPrograms) Close() error {
return _RedirectClose(
p.BpfRedir,
)
}
func _RedirectClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//
//go:embed redirect_bpfeb.o
var _RedirectBytes []byte
+144
View File
@@ -0,0 +1,144 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64
package redirect
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type redirectSockKey struct {
Sip uint32
Dip uint32
Sport uint32
Dport uint32
Family uint32
}
// loadRedirect returns the embedded CollectionSpec for redirect.
func loadRedirect() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_RedirectBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load redirect: %w", err)
}
return spec, err
}
// loadRedirectObjects loads redirect and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *redirectObjects
// *redirectPrograms
// *redirectMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadRedirectObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadRedirect()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// redirectSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type redirectSpecs struct {
redirectProgramSpecs
redirectMapSpecs
redirectVariableSpecs
}
// redirectProgramSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type redirectProgramSpecs struct {
BpfRedir *ebpf.ProgramSpec `ebpf:"bpf_redir"`
}
// redirectMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type redirectMapSpecs struct {
SockOpsMap *ebpf.MapSpec `ebpf:"sock_ops_map"`
TargetIpMap *ebpf.MapSpec `ebpf:"target_ip_map"`
}
// redirectVariableSpecs contains global variables before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type redirectVariableSpecs struct {
}
// redirectObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
type redirectObjects struct {
redirectPrograms
redirectMaps
redirectVariables
}
func (o *redirectObjects) Close() error {
return _RedirectClose(
&o.redirectPrograms,
&o.redirectMaps,
)
}
// redirectMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
type redirectMaps struct {
SockOpsMap *ebpf.Map `ebpf:"sock_ops_map"`
TargetIpMap *ebpf.Map `ebpf:"target_ip_map"`
}
func (m *redirectMaps) Close() error {
return _RedirectClose(
m.SockOpsMap,
m.TargetIpMap,
)
}
// redirectVariables contains all global variables after they have been loaded into the kernel.
//
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
type redirectVariables struct {
}
// redirectPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadRedirectObjects or ebpf.CollectionSpec.LoadAndAssign.
type redirectPrograms struct {
BpfRedir *ebpf.Program `ebpf:"bpf_redir"`
}
func (p *redirectPrograms) Close() error {
return _RedirectClose(
p.BpfRedir,
)
}
func _RedirectClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//
//go:embed redirect_bpfel.o
var _RedirectBytes []byte
+33
View File
@@ -0,0 +1,33 @@
package redirect
import (
"fmt"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
)
type SkMsgOptions struct {
// Program must be an SkMsg BPF program.
Program *ebpf.Program
// Map is the BPF map the program is attached to.
Map int
}
func AttachSkMsg(opts SkMsgOptions) error {
if t := opts.Program.Type(); t != ebpf.SkMsg {
return fmt.Errorf("invalid program type %s, expected SkMsg", t)
}
err := link.RawAttachProgram(link.RawAttachProgramOptions{
Target: opts.Map,
Program: opts.Program,
Attach: ebpf.AttachSkMsgVerdict,
})
if err != nil {
return fmt.Errorf("failed to attach link: %w", err)
}
return nil
}