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

前言

前置阅读:

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

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


统计网卡的速度

代码修改自:


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 parameter
  int ifindex = if_nametoindex(main_config.ifname);
  if (ifindex == 0) {
    perror("if_nametoindex failed");
    exit(EXIT_FAILURE);
  }
  main_config.ifindex = ifindex;

  // print config
  printf("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_LISTS
    VERBATIM
    DEPENDS ${BPF_C_FILE}
    COMMENT "[clang] Building BPF file: ${BPF_C_FILE}")

add_custom_target(generate_bpf_obj ALL
    DEPENDS ${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)

更多阅读

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

da1234cao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值