ebpf教程(4.2):bpf map的使用 -- 统计网速

server/2024/9/24 19:48:06/

文章目录

    • 前言
    • 统计网卡的速度
      • BFP 程序
      • 用户层程序
      • 构建程序
      • 运行
    • 更多阅读

前言

前置阅读:

  • ebpf教程(4.1):XDP程序的加载-CSDN博客

map 可用于内核 BPF 程序和用户应用程序之间实现双向的数据交换, 是 BPF 技术中的重要基础数据结构。

本文使用 BPF_MAP_TYPE_PERCPU_ARRAY 这一类型bpf map,将指定网卡的网速信息上传到用户层。用户层不断的从map中提取网卡的网速信息,并打印。


统计网卡的速度

代码修改自:

  • xdp-tutorial/basic03-map-counter at master · xdp-project/xdp-tutorial
  • xdp-tutorial/basic02-prog-by-name/README.org at master · xdp-project/xdp-tutorial

BFP 程序

BPF 程序,将接收到的数据包个数和数据包长度写入map。

// common.h
#pragma once#include <linux/bpf.h>
#include <linux/types.h>struct data_record {__u64 rx_packets;__u64 rx_bytes;
};#ifndef XDP_ACTION_MAX
#define XDP_ACTION_MAX (XDP_REDIRECT + 1)
#endif
#include <linux/types.h>#include <bpf/bpf_helpers.h>#include "common.h"struct {__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);__type(key, __u32);__type(value, struct data_record);__uint(max_entries, XDP_ACTION_MAX);
} xdp_stats_map SEC(".maps");SEC("xdp")
int xdp_stats_func(struct xdp_md *ctx) {struct data_record *record;__u32 key = XDP_PASS;record = bpf_map_lookup_elem(&xdp_stats_map, &key);if (record == NULL) {return XDP_ABORTED;}__u32 bytes = ctx->data_end - ctx->data;record->rx_packets++;record->rx_bytes += bytes;return XDP_PASS;
}char _license[] SEC("license") = "GPL";

用户层程序

用户层程序:加载 BPF程序,并打印网速。

#include <argp.h>
#include <net/if.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>#include <bpf/libbpf.h>
#include <xdp/libxdp.h>#include "common.h"#define PROG_NAME_MAXSIZE 32
#define NANOSEC_PER_SEC 1000000000 /* 10^9 */struct main_config {char filename[PATH_MAX];char prog_name[PROG_NAME_MAXSIZE];char ifname[IF_NAMESIZE];int ifindex;int interval;
};struct data_record_user {struct data_record record;struct timespec ts;
};static struct main_config main_config;
static volatile bool exiting = false;
struct bpf_object *obj = NULL;static int parse_opt(int key, char *arg, struct argp_state *state) {switch (key) {case 'd':snprintf(main_config.ifname, sizeof(main_config.ifname), "%s", arg);break;case 0:main_config.interval = atoi(arg);break;}return 0;
}static void sig_handler(int sig) { exiting = true; }int load_bpf_and_xdp_attach() {int ret = 0;obj = bpf_object__open(main_config.filename);if (obj == NULL) {perror("bpf_object__open failed");exit(EXIT_FAILURE);}struct xdp_program_opts prog_opts = {};prog_opts.sz = sizeof(struct xdp_program_opts);prog_opts.obj = obj;prog_opts.prog_name = main_config.prog_name;struct xdp_program *prog = xdp_program__create(&prog_opts);if (prog == NULL) {perror("xdp_program__create failed");exit(EXIT_FAILURE);}ret = xdp_program__attach(prog, main_config.ifindex, XDP_MODE_UNSPEC, 0);if (ret != 0) {perror("xdp_program__attach failed");exit(EXIT_FAILURE);}int prog_fd = xdp_program__fd(prog);if (prog_fd < 0) {perror("cant get program fd");exit(EXIT_FAILURE);}return prog_fd;
}static void stats_print(const struct data_record_user *pre_record,const struct data_record_user *record) {__u64 packets, bytes;double period;double pps; /* packets per sec */double bps; /* bits per sec */int i;/* Print for each XDP actions stats */char *fmt = "XDP_PASS %'11lld pkts (%'10.0f pps)"" %'11lld KB (%'6.0f Mbits/s)\n";period =(record->ts.tv_sec - pre_record->ts.tv_sec) +((double)(record->ts.tv_nsec - pre_record->ts.tv_nsec) / NANOSEC_PER_SEC);// If the clock_gettime function uses CLOCK_REALTIME, this may happen if the// system time is modified.if (period <= 0)return;packets = record->record.rx_packets - pre_record->record.rx_packets;pps = packets / period;bytes = record->record.rx_bytes - pre_record->record.rx_bytes;bps = (bytes * 8) / period / 1000000;printf(fmt, record->record.rx_packets, pps,record->record.rx_bytes * 8 / 1000, bps);
}void map_get_value_percpu_array(int mapfd, __u32 key,struct data_record_user *value) {/* For percpu maps, userspace gets a value per possible CPU */int ret;int nr_cpus = libbpf_num_possible_cpus();struct data_record records[nr_cpus];ret = clock_gettime(CLOCK_MONOTONIC, &value->ts);if (ret != 0) {perror("clock_gettime failed");exit(EXIT_FAILURE);}ret = bpf_map_lookup_elem(mapfd, &key, records);if (ret != 0) {perror("bpf_map_lookup_elem failed");exit(EXIT_FAILURE);}/* Sum values from each CPU */__u64 sum_bytes = 0;__u64 sum_pkts = 0;for (int i = 0; i < nr_cpus; i++) {sum_pkts += records[i].rx_packets;sum_bytes += records[i].rx_bytes;}value->record.rx_packets = sum_pkts;value->record.rx_bytes = sum_bytes;
}void speed_poll(int mapfd) {struct data_record_user record = {};struct data_record_user pre_record = {};sleep(main_config.interval);map_get_value_percpu_array(mapfd, XDP_PASS, &record);while (!exiting) {pre_record = record;map_get_value_percpu_array(mapfd, XDP_PASS, &record);stats_print(&pre_record, &record);sleep(main_config.interval);}
}int main(int argc, char *argv[]) {int ret = 0;memset(&main_config, 0, sizeof(main_config));snprintf(main_config.filename, sizeof(main_config.filename), "%s","xdp_prog_kernel.o");snprintf(main_config.prog_name, sizeof(main_config.prog_name), "%s","xdp_stats_func");main_config.interval = 2;struct argp_option options[] = {{"dev", 'd', "device name", 0, "Set the network card name"},{"interval", 0, "statistical interval", 0,"Set the statistical interval"},{0},};struct argp argp = {.options = options,.parser = parse_opt,};argp_parse(&argp, argc, argv, 0, 0, 0);// check parameterint ifindex = if_nametoindex(main_config.ifname);if (ifindex == 0) {perror("if_nametoindex failed");exit(EXIT_FAILURE);}main_config.ifindex = ifindex;// print configprintf("prog name: %s\n", main_config.prog_name);printf("choice dev: %s\n", main_config.ifname);printf("%s's index: %d\n", main_config.ifname, ifindex);printf("sampling interval for statistics: %d\n", main_config.interval);/* Cleaner handling of Ctrl-C */signal(SIGINT, sig_handler);signal(SIGTERM, sig_handler);int prog_fd = load_bpf_and_xdp_attach();// prog->bpf_obj is null;xdp_program__from_fd does not fill in this structure// internally//   struct xdp_program *prog = xdp_program__from_fd(prog_fd);//   struct bpf_object *obj = xdp_program__bpf_obj(prog);if (obj == NULL) {perror("xdp_program__bpf_obj failed");exit(EXIT_FAILURE);}struct bpf_map *map = bpf_object__find_map_by_name(obj, "xdp_stats_map");if (map == NULL) {perror("bpf_object__find_map_by_name failed");exit(EXIT_FAILURE);}int map_fd = bpf_map__fd(map);speed_poll(map_fd);cleanup:struct xdp_multiprog *mp = xdp_multiprog__get_from_ifindex(ifindex);ret = xdp_multiprog__detach(mp);if (ret != 0) {perror("xdp_multiprog__detach failed.");exit(EXIT_FAILURE);}bpf_object__close(obj);
}

构建程序

cmake_minimum_required(VERSION 3.10)project(xdp-load-prog)find_package(PkgConfig)
pkg_check_modules(LIBBPF REQUIRED libbpf)
pkg_check_modules(LIBXDP REQUIRED libxdp)find_path(ASM_TYPES_H_PATH NAMES asm/types.h PATHS /usr/include/x86_64-linux-gnu)
if(ASM_TYPES_H_PATH)message(STATUS "Found asm/types.h at ${ASM_TYPES_H_PATH}")include_directories(${ASM_TYPES_H_PATH})
else()message(FATAL_ERROR "asm/types.h not found")
endif()set(BPF_C_FILE ${CMAKE_CURRENT_SOURCE_DIR}/xdp_prog_kernel.c)
set(BPF_O_FILE ${CMAKE_CURRENT_BINARY_DIR}/xdp_prog_kernel.o)
add_custom_command(OUTPUT ${BPF_O_FILE}COMMAND clang -g -O2 -target bpf -I${ASM_TYPES_H_PATH} ${CLANG_SYSTEM_INCLUDES} -c ${BPF_C_FILE} -o ${BPF_O_FILE}COMMAND_EXPAND_LISTSVERBATIMDEPENDS ${BPF_C_FILE}COMMENT "[clang] Building BPF file: ${BPF_C_FILE}")add_custom_target(generate_bpf_obj ALLDEPENDS ${BPF_O_FILE}
)add_executable(xdp_load_and_stats xdp_load_and_stats.c)
target_link_libraries(xdp_load_and_stats PRIVATE ${LIBBPF_LIBRARIES} ${LIBXDP_LIBRARIES})

运行

我使用 iperf3ens19 网卡发送数据包,以提供流量。

# 每个两秒,打印下指定网卡的网速
./xdp_load_and_stats --dev=ens19
prog name: xdp_stats_func
choice dev: ens19
ens19's index: 3
sampling interval for statistics: 2
libbpf: elf: skipping unrecognized data section(7) xdp_metadata
libbpf: elf: skipping unrecognized data section(7) xdp_metadata
libbpf: elf: skipping unrecognized data section(7) xdp_metadata
libbpf: elf: skipping unrecognized data section(7) xdp_metadata
XDP_PASS           0 pkts (         0 pps)           0 KB (     0 Mbits/s)
XDP_PASS     1142497 pkts (    571216 pps)    13837763 KB (  6918 Mbits/s)
XDP_PASS     2625795 pkts (    741594 pps)    31803445 KB (  8982 Mbits/s)
XDP_PASS     3543266 pkts (    458703 pps)    42915796 KB (  5556 Mbits/s)

更多阅读

  • 揭秘 BPF map 前生今世 | Head First eBPF
  • BPF 进阶笔记(二):BPF Map 类型详解:使用场景、程序示例

http://www.ppmy.cn/server/103726.html

相关文章

Vue 满屏纵向轮播图

目录 前言轮播图效果展示具体实现实现思路具体代码前言 今天汇总一个需求,还是之前写的,要求写一个满屏的轮播图,准确的说,是鼠标滑动到轮播图的时候,轮播图固定在屏幕上,随着其中的轮播子项遍历结束后,解除固定的效果。原本我最开始想直接修改Element-UI的组件的,但是…

leetcode_59. 螺旋矩阵 II

59. 螺旋矩阵 II 题目描述&#xff1a;给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a;​ 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&…

基因组学系列4:参考转录本数据库MANE

1. 参考转录本数据库MANE简介 为了促进临床参照的一致性&#xff0c;美国国家生物技术信息中心( NCBI)和欧洲分子生物学实验室-欧洲生物信息学研究所(EMBL-EBI)合作发布了参考转录本数据库MANE&#xff08;Matched Annotation from the NCBI and EMBL-EBI&#xff09;&#xf…

【python】逐步回归(多元线性回归模型中的应用)

文章目录 前言一、逐步回归1. 前进法&#xff08;Forward Selection&#xff09;2. 后退法&#xff08;Backward Elimination&#xff09;3. 逐步回归法&#xff08;Stepwise Regression&#xff09; 二、示例三、代码实现----python 前言 Matlab中逐步回归的实现可以使用 Mat…

极限02:两个重要极限

1.夹逼准则 定义&#xff1a;设{ a n a_n an​}, { b n b_n bn​}, { c n c_n cn​}为实数列&#xff0c; a n ≤ b n ≤ c n a_n≤b_n≤c_n an​≤bn​≤cn​, 且 lim ⁡ n → ∞ a n lim ⁡ n → ∞ c n l \lim_{n \to \infty} a_n \lim_{n \to \infty} c_n l n→∞lim​…

平衡日常工作与提升式学习话题有感

文章目录 前言1.工作是什么&#xff1f;2.怎么提升技术&#xff1f;3.工作/学习与生活的平衡总结 前言 这篇博客是针对程序员如何平衡日常编码工作与提升式学习&#xff1f;这个话题进行的个人观点阐述&#xff0c;个人所思所想罢了。 刚毕业没几年&#xff0c;水平有限&#…

运行微信小程序报错:Bad attr data-event-opts with message

问题 使用uniapp 编译&#xff0c;运行微信小程序环境时&#xff0c;报错 Bad attr data-event-opts with message。&#xff08;这个错误报错原因很多&#xff0c;这里只解决一个&#xff09; 原因 原因是&#xff1a;代码中有&#xff1a; :key"swiperList i"…

RAM(随机存取存储器)都有哪些?(超详细)

目录 RAM的特点 RAM的类型 1. SRAM&#xff08;静态随机存取存储器&#xff09; 2. DRAM&#xff08;动态随机存取存储器&#xff09; 3. SDRAM&#xff08;同步动态随机存取存储器&#xff09; 4. DDR SDRAM&#xff08;双倍数据速率同步动态随机存取存储器&#xff09;…