如何创建接收到的数据包与允许通过的数据包的图表
How to create a graph of packets received vs packets allowed to pass
我有一个 XDP 程序,我正在丢弃环回设备(将来会使用物理设备)上收到的所有其他数据包。我想使用每秒数据包创建一个图表,显示设备(或 xdp 程序)接收了多少数据包与允许通过多少数据包(XDP_PASS)。我的目标是开发该程序以减轻 udp 洪水攻击,因此我需要收集此类数据来衡量其性能。
我将重点关注从 XDP 到用户空间的指标传输部分,因为绘制数据本身是一个相当大的主题。
如果你只关心 PASS/DROP 整体,我可以从 xdp-tutorial 推荐 basic03-map-count。
本教程的最后“任务”是将代码转换为每个 CPU 示例。对于 DDoS 相关程序,这是相当关键的,因为使用共享地图会导致阻塞。这是此类程序的示例:
#include <linux/bpf.h>
#define SEC(NAME) __attribute__((section(NAME), used))
#define XDP_MAX_ACTION 5
// From https://github.com/libbpf/libbpf/blob/master/src/bpf_helper_defs.h
static void *(*bpf_map_lookup_elem)(void *map, const void *key) = (void *) 1;
struct bpf_map_def {
unsigned int type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
};
struct datarec {
__u64 rx_packets;
};
struct bpf_map_def SEC("maps") xdp_stats_map = {
.type = BPF_MAP_TYPE_PERCPU_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(struct datarec),
.max_entries = XDP_MAX_ACTION,
};
SEC("xdp_stats1")
int xdp_stats1_func(struct xdp_md *ctx)
{
// void *data_end = (void *)(long)ctx->data_end;
// void *data = (void *)(long)ctx->data;
struct datarec *rec;
__u32 action = XDP_PASS; /* XDP_PASS = 2 */
// TODO add some logic, instread of returning directly, just set action to XDP_PASS or XDP_BLOCK
/* Lookup in kernel BPF-side return pointer to actual data record */
rec = bpf_map_lookup_elem(&xdp_stats_map, &action);
if (!rec)
return XDP_ABORTED;
// Since xdp_stats_map is a per-CPU map, every logical-CPU/Core gets its own memory,
// we can safely increment without raceconditions or need for locking.
rec->rx_packets++;
return action;
}
char _license[] SEC("license") = "GPL";
您会注意到我们使用相同的地图键,与时间无关。这种程序需要用户空间以 1 秒的间隔轮询地图并计算差异。如果您需要 100% 准确的统计数据或不想每秒轮询数据,您可以在密钥中包含时间:
#include <linux/bpf.h>
#define SEC(NAME) __attribute__((section(NAME), used))
#define XDP_MAX_ACTION 5
// From https://github.com/libbpf/libbpf/blob/master/src/bpf_helper_defs.h
static void *(*bpf_map_lookup_elem)(void *map, const void *key) = (void *) 1;
static long (*bpf_map_update_elem)(void *map, const void *key, const void *value, __u64 flags) = (void *) 2;
static __u64 (*bpf_ktime_get_ns)(void) = (void *) 5;
struct bpf_map_def {
unsigned int type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
};
struct timekey {
__u32 action;
__u32 second;
};
struct datarec {
__u64 rx_packets;
__u64 last_update;
};
struct bpf_map_def SEC("maps") xdp_stats_map = {
.type = BPF_MAP_TYPE_PERCPU_HASH,
.key_size = sizeof(struct timekey),
.value_size = sizeof(struct datarec),
.max_entries = XDP_MAX_ACTION * 60,
};
#define SECOND_NS 1000000000
SEC("xdp")
int xdp_stats1_func(struct xdp_md *ctx)
{
// void *data_end = (void *)(long)ctx->data_end;
// void *data = (void *)(long)ctx->data;
struct datarec *rec;
struct timekey key;
__u64 now;
key.action = XDP_PASS; /* XDP_PASS = 2 */
// TODO add some logic, instread of returning directly, just set action to XDP_PASS or XDP_BLOCK
now = bpf_ktime_get_ns();
key.second = (now / SECOND_NS) % 60;
/* Lookup in kernel BPF-side return pointer to actual data record */
rec = bpf_map_lookup_elem(&xdp_stats_map, &key);
if (rec) {
// If the last update to this key was more than 1 second ago, we are reusing the key, reset it.
if (rec->last_update - now > SECOND_NS) {
rec->rx_packets = 0;
}
rec->last_update = now;
rec->rx_packets++;
} else {
struct datarec new_rec = {
.rx_packets = 1,
.last_update = now,
};
bpf_map_update_elem(&xdp_stats_map, &key, &new_rec, BPF_ANY);
}
return key.action;
}
char _license[] SEC("license") = "GPL";
还制作了一个用户空间示例,展示了如何从第二个示例中读取地图。 (对 Go 感到抱歉,我的 C 技能无法通过简单的 eBPF 程序):
package main
import (
"bytes"
"embed"
"fmt"
"os"
"os/signal"
"runtime"
"time"
"github.com/dylandreimerink/gobpfld"
"github.com/dylandreimerink/gobpfld/bpftypes"
"github.com/dylandreimerink/gobpfld/ebpf"
)
//go:embed src/xdp
var f embed.FS
func main() {
elfFileBytes, err := f.ReadFile("src/xdp")
if err != nil {
fmt.Fprintf(os.Stderr, "error opening ELF file: %s\n", err.Error())
os.Exit(1)
}
elf, err := gobpfld.LoadProgramFromELF(bytes.NewReader(elfFileBytes), gobpfld.ELFParseSettings{
TruncateNames: true,
})
if err != nil {
fmt.Fprintf(os.Stderr, "error while reading ELF file: %s\n", err.Error())
os.Exit(1)
}
prog := elf.Programs["xdp_stats1_func"].(*gobpfld.ProgramXDP)
log, err := prog.Load(gobpfld.ProgXDPLoadOpts{
VerifierLogLevel: bpftypes.BPFLogLevelVerbose,
})
if err != nil {
fmt.Println(log)
fmt.Fprintf(os.Stderr, "error while loading progam: %s\n", err.Error())
os.Exit(1)
}
err = prog.Attach(gobpfld.ProgXDPAttachOpts{
InterfaceName: "lo",
})
if err != nil {
fmt.Fprintf(os.Stderr, "error while loading progam: %s\n", err.Error())
os.Exit(1)
}
defer func() {
prog.XDPLinkDetach(gobpfld.BPFProgramXDPLinkDetachSettings{
All: true,
})
}()
statMap := prog.Maps["xdp_stats_map"].(*gobpfld.HashMap)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt)
ticker := time.NewTicker(1 * time.Second)
done := false
for !done {
select {
case <-ticker.C:
var key MapKey
// Since the map is a per-CPU type, the value we will read is an array with the same amount of elements
// as logical CPU's
value := make([]MapValue, runtime.NumCPU())
// Map keyed by second, index keyed by action, value = count
userMap := map[uint32][]uint32{}
latest := uint64(0)
latestSecond := int32(0)
gobpfld.MapIterForEach(statMap.Iterator(), &key, &value, func(_, _ interface{}) error {
// Sum all values
total := make([]uint32, 5)
for _, val := range value {
total[key.Action] += uint32(val.PktCount)
// Record the latest changed key, this only works if we have at least 1 pkt/s.
if latest < val.LastUpdate {
latest = val.LastUpdate
latestSecond = int32(key.Second)
}
}
userMap[key.Second] = total
return nil
})
// We wan't the last second, not the current one, since it is still changing
latestSecond--
if latestSecond < 0 {
latestSecond += 60
}
values := userMap[uint32(latestSecond)]
fmt.Printf("%02d: aborted: %d, dropped: %d, passed: %d, tx'ed: %d, redirected: %d\n",
latestSecond,
values[ebpf.XDP_ABORTED],
values[ebpf.XDP_DROP],
values[ebpf.XDP_PASS],
values[ebpf.XDP_TX],
values[ebpf.XDP_REDIRECT],
)
case <-sigChan:
done = true
}
}
}
type MapKey struct {
Action uint32
Second uint32
}
type MapValue struct {
PktCount uint64
LastUpdate uint64
}
我有一个 XDP 程序,我正在丢弃环回设备(将来会使用物理设备)上收到的所有其他数据包。我想使用每秒数据包创建一个图表,显示设备(或 xdp 程序)接收了多少数据包与允许通过多少数据包(XDP_PASS)。我的目标是开发该程序以减轻 udp 洪水攻击,因此我需要收集此类数据来衡量其性能。
我将重点关注从 XDP 到用户空间的指标传输部分,因为绘制数据本身是一个相当大的主题。
如果你只关心 PASS/DROP 整体,我可以从 xdp-tutorial 推荐 basic03-map-count。
本教程的最后“任务”是将代码转换为每个 CPU 示例。对于 DDoS 相关程序,这是相当关键的,因为使用共享地图会导致阻塞。这是此类程序的示例:
#include <linux/bpf.h>
#define SEC(NAME) __attribute__((section(NAME), used))
#define XDP_MAX_ACTION 5
// From https://github.com/libbpf/libbpf/blob/master/src/bpf_helper_defs.h
static void *(*bpf_map_lookup_elem)(void *map, const void *key) = (void *) 1;
struct bpf_map_def {
unsigned int type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
};
struct datarec {
__u64 rx_packets;
};
struct bpf_map_def SEC("maps") xdp_stats_map = {
.type = BPF_MAP_TYPE_PERCPU_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(struct datarec),
.max_entries = XDP_MAX_ACTION,
};
SEC("xdp_stats1")
int xdp_stats1_func(struct xdp_md *ctx)
{
// void *data_end = (void *)(long)ctx->data_end;
// void *data = (void *)(long)ctx->data;
struct datarec *rec;
__u32 action = XDP_PASS; /* XDP_PASS = 2 */
// TODO add some logic, instread of returning directly, just set action to XDP_PASS or XDP_BLOCK
/* Lookup in kernel BPF-side return pointer to actual data record */
rec = bpf_map_lookup_elem(&xdp_stats_map, &action);
if (!rec)
return XDP_ABORTED;
// Since xdp_stats_map is a per-CPU map, every logical-CPU/Core gets its own memory,
// we can safely increment without raceconditions or need for locking.
rec->rx_packets++;
return action;
}
char _license[] SEC("license") = "GPL";
您会注意到我们使用相同的地图键,与时间无关。这种程序需要用户空间以 1 秒的间隔轮询地图并计算差异。如果您需要 100% 准确的统计数据或不想每秒轮询数据,您可以在密钥中包含时间:
#include <linux/bpf.h>
#define SEC(NAME) __attribute__((section(NAME), used))
#define XDP_MAX_ACTION 5
// From https://github.com/libbpf/libbpf/blob/master/src/bpf_helper_defs.h
static void *(*bpf_map_lookup_elem)(void *map, const void *key) = (void *) 1;
static long (*bpf_map_update_elem)(void *map, const void *key, const void *value, __u64 flags) = (void *) 2;
static __u64 (*bpf_ktime_get_ns)(void) = (void *) 5;
struct bpf_map_def {
unsigned int type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
};
struct timekey {
__u32 action;
__u32 second;
};
struct datarec {
__u64 rx_packets;
__u64 last_update;
};
struct bpf_map_def SEC("maps") xdp_stats_map = {
.type = BPF_MAP_TYPE_PERCPU_HASH,
.key_size = sizeof(struct timekey),
.value_size = sizeof(struct datarec),
.max_entries = XDP_MAX_ACTION * 60,
};
#define SECOND_NS 1000000000
SEC("xdp")
int xdp_stats1_func(struct xdp_md *ctx)
{
// void *data_end = (void *)(long)ctx->data_end;
// void *data = (void *)(long)ctx->data;
struct datarec *rec;
struct timekey key;
__u64 now;
key.action = XDP_PASS; /* XDP_PASS = 2 */
// TODO add some logic, instread of returning directly, just set action to XDP_PASS or XDP_BLOCK
now = bpf_ktime_get_ns();
key.second = (now / SECOND_NS) % 60;
/* Lookup in kernel BPF-side return pointer to actual data record */
rec = bpf_map_lookup_elem(&xdp_stats_map, &key);
if (rec) {
// If the last update to this key was more than 1 second ago, we are reusing the key, reset it.
if (rec->last_update - now > SECOND_NS) {
rec->rx_packets = 0;
}
rec->last_update = now;
rec->rx_packets++;
} else {
struct datarec new_rec = {
.rx_packets = 1,
.last_update = now,
};
bpf_map_update_elem(&xdp_stats_map, &key, &new_rec, BPF_ANY);
}
return key.action;
}
char _license[] SEC("license") = "GPL";
还制作了一个用户空间示例,展示了如何从第二个示例中读取地图。 (对 Go 感到抱歉,我的 C 技能无法通过简单的 eBPF 程序):
package main
import (
"bytes"
"embed"
"fmt"
"os"
"os/signal"
"runtime"
"time"
"github.com/dylandreimerink/gobpfld"
"github.com/dylandreimerink/gobpfld/bpftypes"
"github.com/dylandreimerink/gobpfld/ebpf"
)
//go:embed src/xdp
var f embed.FS
func main() {
elfFileBytes, err := f.ReadFile("src/xdp")
if err != nil {
fmt.Fprintf(os.Stderr, "error opening ELF file: %s\n", err.Error())
os.Exit(1)
}
elf, err := gobpfld.LoadProgramFromELF(bytes.NewReader(elfFileBytes), gobpfld.ELFParseSettings{
TruncateNames: true,
})
if err != nil {
fmt.Fprintf(os.Stderr, "error while reading ELF file: %s\n", err.Error())
os.Exit(1)
}
prog := elf.Programs["xdp_stats1_func"].(*gobpfld.ProgramXDP)
log, err := prog.Load(gobpfld.ProgXDPLoadOpts{
VerifierLogLevel: bpftypes.BPFLogLevelVerbose,
})
if err != nil {
fmt.Println(log)
fmt.Fprintf(os.Stderr, "error while loading progam: %s\n", err.Error())
os.Exit(1)
}
err = prog.Attach(gobpfld.ProgXDPAttachOpts{
InterfaceName: "lo",
})
if err != nil {
fmt.Fprintf(os.Stderr, "error while loading progam: %s\n", err.Error())
os.Exit(1)
}
defer func() {
prog.XDPLinkDetach(gobpfld.BPFProgramXDPLinkDetachSettings{
All: true,
})
}()
statMap := prog.Maps["xdp_stats_map"].(*gobpfld.HashMap)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt)
ticker := time.NewTicker(1 * time.Second)
done := false
for !done {
select {
case <-ticker.C:
var key MapKey
// Since the map is a per-CPU type, the value we will read is an array with the same amount of elements
// as logical CPU's
value := make([]MapValue, runtime.NumCPU())
// Map keyed by second, index keyed by action, value = count
userMap := map[uint32][]uint32{}
latest := uint64(0)
latestSecond := int32(0)
gobpfld.MapIterForEach(statMap.Iterator(), &key, &value, func(_, _ interface{}) error {
// Sum all values
total := make([]uint32, 5)
for _, val := range value {
total[key.Action] += uint32(val.PktCount)
// Record the latest changed key, this only works if we have at least 1 pkt/s.
if latest < val.LastUpdate {
latest = val.LastUpdate
latestSecond = int32(key.Second)
}
}
userMap[key.Second] = total
return nil
})
// We wan't the last second, not the current one, since it is still changing
latestSecond--
if latestSecond < 0 {
latestSecond += 60
}
values := userMap[uint32(latestSecond)]
fmt.Printf("%02d: aborted: %d, dropped: %d, passed: %d, tx'ed: %d, redirected: %d\n",
latestSecond,
values[ebpf.XDP_ABORTED],
values[ebpf.XDP_DROP],
values[ebpf.XDP_PASS],
values[ebpf.XDP_TX],
values[ebpf.XDP_REDIRECT],
)
case <-sigChan:
done = true
}
}
}
type MapKey struct {
Action uint32
Second uint32
}
type MapValue struct {
PktCount uint64
LastUpdate uint64
}