bfp笔记

BPF执行原理

  1. 编译BPF程序:BPF程序可以使用C语言、BPF汇编语言或编译器插件进行编写。编写完成后,BPF程序会被编译为一段机器码,该机器码是一组低级指令,类似于虚拟机指令。

  2. 加载BPF程序:BPF程序通常被封装在一个BPF对象文件中,该文件包含了BPF程序的机器码、符号表、重定位表等元数据。使用libbpf库或其他BPF工具,可以将BPF对象文件加载到内核中。

  3. 验证BPF程序:在加载BPF程序之前,内核会对其进行验证。这个过程包括检查程序的合法性、查找和解析程序中的符号等。验证确保BPF程序不会破坏内核的稳定性和安全性。

  4. JIT编译(可选):为了提高执行速度,BPF程序可以进行即时编译(JIT)。JIT编译器会将BPF程序的机器码转换为与当前CPU架构兼容的本地机器码。这样,BPF程序就可以直接在CPU上运行,而不需要解释执行。

  5. 执行BPF程序:一旦BPF程序被加载和编译,内核就可以执行该程序了。BPF程序通常与网络数据包处理相关,可以被挂钩到网络接口的不同阶段,如入站、出站、转发等。当网络数据包经过被挂钩的阶段时,内核会执行相应的BPF程序来过滤或处理数据包。

  6. 程序返回和结果处理:BPF程序执行完成后,会返回一个结果。这个结果可以是允许或拒绝数据包通过,也可以是对数据包进行修改或采集一些元数据等。内核会根据BPF程序的返回值来决定如何处理数据包。

BPF指令由Linux内核的BPF运行时模块执行,具体来说,该运行时模块提供两种执行机制:一个解释器和一个将BPF指令动态转换为本地化指令的即时编译器(JIT)。注意只有当BPF程序通过解释器执行时其实现才是一种虚拟机。当前JIT启用后BFP指令将通过JIT编译后像如何其它本地内核代码一样,直接在处理器上运行。如果没有启用JIT则经由解释器执行。相比于解释器执行,JIT执行的性能更好。一些发行版在x86架构上JIT是默认开启的,完全移除了内核中解释器的实现。

 BPF Hooks

SEC关键字

在BPF中,SEC是一个用于指定BPF程序属性的关键字。SEC是Section的缩写,表示代码段或数据段。SEC关键字用于将BPF程序中的函数或变量放置在特定的代码段或数据段中,以便在加载和执行时进行控制。

BPF程序可以包含多个函数和变量,而SEC关键字用于对它们进行分组和分类。常用的SEC关键字包括:

  1. SEC("kprobe/"):用于将BPF程序挂钩到内核的kprobe(内核探针)上。kprobe允许在内核函数的入口点处执行自定义的BPF程序。

  2. SEC("kretprobe/"):用于将BPF程序挂钩到内核的kretprobe(内核返回探针)上。kretprobe允许在内核函数的返回点处执行自定义的BPF程序。

  3. SEC("tracepoint/"):用于将BPF程序挂钩到内核的tracepoint(跟踪点)上。tracepoint是内核中预定义的事件,可以用于跟踪内核的各种操作。

  4. SEC("perf_event/"):用于将BPF程序挂钩到内核的性能事件上。perf_event是内核中的性能事件接口,可以用于测量和跟踪系统的性能指标。

  5. SEC(“tracepoint”):指定函数为跟踪点(tracepoint)。跟踪点是BPF程序中用于在Linux内核的跟踪事件中插入代码的部分。

  6. SEC(“maps”):指定变量为BPF映射(maps)。BPF映射是一种数据结构,用于在BPF程序和用户空间之间共享数据。

  7. SEC(“license”):指定变量为BPF程序的许可证(license)。许可证用于说明BPF程序的使用权限和限制。

  8. SEC("xdp_sock")是一个特殊的BPF宏,用于指定一个BPF程序的类型为XDP(eXpress Data Path)套接字型。在BPF程序中使用SEC("xdp_sock")宏,可以将该程序挂钩到Linux内核中的XDP处理路径上

BPF映射

在eBPF(extended Berkeley Packet Filter)中,映射(Map)是一种用于在用户空间和内核空间之间共享数据的机制。映射可以看作是一种键-值存储结构,用于在eBPF程序执行期间读取和写入数据。

映射提供了一种将数据从用户空间传递到内核空间的方式,以及将数据从内核空间传递回用户空间的方式。映射可以从用户空间被读取和修改,并且在eBPF程序执行期间可以在内核空间中进行读取和修改。

所有 map 类型的定义:

enum bpf_map_type {
    BPF_MAP_TYPE_UNSPEC,
    BPF_MAP_TYPE_HASH,
    BPF_MAP_TYPE_ARRAY,
    BPF_MAP_TYPE_PROG_ARRAY,
    BPF_MAP_TYPE_PERF_EVENT_ARRAY,
    BPF_MAP_TYPE_PERCPU_HASH,
    BPF_MAP_TYPE_PERCPU_ARRAY,
    BPF_MAP_TYPE_STACK_TRACE,
    BPF_MAP_TYPE_CGROUP_ARRAY,
    BPF_MAP_TYPE_LRU_HASH,
    BPF_MAP_TYPE_LRU_PERCPU_HASH,
    BPF_MAP_TYPE_LPM_TRIE,
    BPF_MAP_TYPE_ARRAY_OF_MAPS,
    BPF_MAP_TYPE_HASH_OF_MAPS,
    BPF_MAP_TYPE_DEVMAP,
    BPF_MAP_TYPE_SOCKMAP,
    BPF_MAP_TYPE_CPUMAP,
    BPF_MAP_TYPE_XSKMAP,
    BPF_MAP_TYPE_SOCKHASH,
    BPF_MAP_TYPE_CGROUP_STORAGE,
    BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
    BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
    BPF_MAP_TYPE_QUEUE,
    BPF_MAP_TYPE_STACK,
    BPF_MAP_TYPE_SK_STORAGE,
    BPF_MAP_TYPE_DEVMAP_HASH,
    BPF_MAP_TYPE_STRUCT_OPS,
    BPF_MAP_TYPE_RINGBUF,
    BPF_MAP_TYPE_INODE_STORAGE,

};

创建BPF maps 

BPF_MAP_TYPE_ARRAY类型的map创建

struct {
//指定了映射的类型为数组。BPF_MAP_TYPE_ARRAY是一个预定义的宏,用于表示数组映射。
	__uint(type, BPF_MAP_TYPE_ARRAY);
//指定了映射键的类型为u32。
	__type(key, u32);
//指定了映射值的类型为long
	__type(value, long);
//指定了映射的最大条目数为256,即映射中可以存储的最大元素数量
	__uint(max_entries, 256);
//指定了映射应该被放置在特定的eBPF映射节中。这是为了在加载eBPF程序时将映射与相应的节关联起来
} my_map SEC(".maps");

 BPF_MAP_TYPE_HASH类型的map创建

struct pair {
	long packets;
	long bytes;
};

struct {
    __uint(type, BPF_MAP_TYPE_HASH);  // BPF map 类型
    __type(key, __be32);              // 目的 IP 地址
    __type(value, struct pair);       // 包数和字节数
    __uint(max_entries, 1024);        // 最大 entry 数量
} hash_map SEC(".maps");

SEC(".maps")bpf_create_map都是用于创建eBPF映射(Map)的机制,但它们的原理和实现方式略有不同。

  1. SEC(".maps")原理:SEC(".maps")是一个编译器指示,用于在eBPF程序中定义映射的结构和初始化数据。在编译eBPF程序时,编译器会将使用SEC(".maps")指示定义的映射结构和初始化数据放置在特定的程序节(Section)中。当eBPF程序加载到内核时,内核会解析程序节,并根据SEC(".maps")指示找到映射的定义和初始化数据。然后,内核根据这些信息来创建映射,并将其挂载到内核的映射表中。这种方式适用于在编译时确定映射结构和数据的情况。

  2. bpf_create_map原理:bpf_create_map是一个运行时的系统调用,用于在eBPF程序运行时动态创建映射。当程序调用bpf_create_map系统调用时,内核会根据传递的映射类型和配置参数,创建一个新的映射对象。映射对象在内核中分配内存,并初始化相关数据结构。然后,内核会返回一个映射的文件描述符(File Descriptor)给程序,程序可以通过该文件描述符来对映射进行操作。这种方式适用于需要在程序运行时根据需求动态创建映射的情况。

更新元素

bpf_map_update_elem是eBPF中的一个函数,用于在eBPF程序中更新映射中的元素或插入新的元素

int bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags);
  • map是一个指向映射的指针,表示要进行更新或插入操作的映射。

  • key是一个指向要更新或插入的元素的键的指针。

  • value是一个指向要更新或插入的元素的值的指针。

  • flags是一个用于指定更新操作的标志位。可以使用BPF_NOEXIST标志表示仅在键不存在时进行插入操作,使用BPF_EXIST标志表示仅在键已存在时进行更新操作。

  • 该函数返回一个整数,表示操作的结果。如果操作成功,则返回0;如果操作失败,则返回一个负数错误代码。

读取元素

bpf_map_lookup_elem是eBPF中的一个函数,用于在eBPF程序中查找映射中的元素

void *bpf_map_lookup_elem(struct bpf_map *map, const void *key);
  • map是一个指向映射的指针,表示要进行查找的映射。

  • key是一个指向要查找的元素的键的指针。

  • 该函数返回一个void指针,指向映射中与给定键匹配的元素的值。如果找到匹配的元素,则返回对应的值的指针;如果未找到匹配的元素,则返回NULL。

 迭代遍历元素和查找

bpf_map_lookup_elem 是eBPF中的一个函数,用于在eBPF程序中查找映射中的元素。

该函数的原型如下:

void *bpf_map_lookup_elem(struct bpf_map *map, const void *key);
  • map是一个指向映射的指针,表示要进行查找的映射。

  • key是一个指向要查找的元素的键的指针。

该函数返回一个void指针,指向映射中与给定键匹配的元素的值。如果找到匹配的元素,则返回对应的值的指针;如果未找到匹配的元素,则返回NULL。

bpf_map_get_next_key 是eBPF中的一个函数,用于在eBPF程序中获取映射中下一个键的值。

该函数的原型如下:

int bpf_map_get_next_key(struct bpf_map *map, const void *key, void *next_key);
  • map是一个指向映射的指针,表示要进行操作的映射。

  • key是一个指向当前键的指针,表示要获取下一个键的值。

  • next_key是一个指向存储下一个键的值的缓冲区的指针。

该函数返回一个整数,表示操作的结果。如果成功获取到下一个键的值,则返回0;如果没有下一个键或发生其他错误,则返回一个负数错误代码。

删除

bpf_map_delete_elem 是eBPF中的一个函数,用于在eBPF程序中删除映射中的元素。

该函数的原型如下:

int bpf_map_delete_elem(struct bpf_map *map, const void *key);
  • map是一个指向映射的指针,表示要进行操作的映射。

  • key是一个指向要删除的元素的键的指针。

该函数返回一个整数,表示操作的结果。如果成功删除元素,则返回0;如果未找到匹配的元素或发生其他错误,则返回一个负数错误代码。

案例1

内核态代码

// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2017 Facebook
 */
#include <uapi/linux/bpf.h>
#include <bpf/bpf_helpers.h>

struct syscalls_enter_open_args {
	unsigned long long unused;
	long syscall_nr;
	long filename_ptr;
	long flags;
	long mode;
};

struct syscalls_exit_open_args {
	unsigned long long unused;
	long syscall_nr;
	long ret;
};

struct {
	__uint(type, BPF_MAP_TYPE_ARRAY);
	__type(key, u32);
	__type(value, u32);
	__uint(max_entries, 1);
} enter_open_map SEC(".maps");

struct {
	__uint(type, BPF_MAP_TYPE_ARRAY);
	__type(key, u32);
	__type(value, u32);
	__uint(max_entries, 1);
} exit_open_map SEC(".maps");

static __always_inline void count(void *map)
{
	u32 key = 0;
	u32 *value, init_val = 1;

	value = bpf_map_lookup_elem(map, &key);
	if (value)
		*value += 1;
	else
		bpf_map_update_elem(map, &key, &init_val, BPF_NOEXIST);
}

SEC("tracepoint/syscalls/sys_enter_open")
int trace_enter_open(struct syscalls_enter_open_args *ctx)
{
	count(&enter_open_map);
	return 0;
}

SEC("tracepoint/syscalls/sys_enter_openat")
int trace_enter_open_at(struct syscalls_enter_open_args *ctx)
{
	count(&enter_open_map);
	return 0;
}

SEC("tracepoint/syscalls/sys_exit_open")
int trace_enter_exit(struct syscalls_exit_open_args *ctx)
{
	count(&exit_open_map);
	return 0;
}

SEC("tracepoint/syscalls/sys_exit_openat")
int trace_enter_exit_at(struct syscalls_exit_open_args *ctx)
{
	count(&exit_open_map);
	return 0;
}

用户态代码

// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2017 Facebook
 */
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/perf_event.h>
#include <errno.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>

/* This program verifies bpf attachment to tracepoint sys_enter_* and sys_exit_*.
 * This requires kernel CONFIG_FTRACE_SYSCALLS to be set.
 */

static void usage(const char *cmd)
{
	printf("USAGE: %s [-i num_progs] [-h]\n", cmd);
	printf("       -i num_progs      # number of progs of the test\n");
	printf("       -h                # help\n");
}

static void verify_map(int map_id,const char* name)
{
	__u32 key = 0;
	__u32 val;

	if (bpf_map_lookup_elem(map_id, &key, &val) != 0) {
		fprintf(stderr, "map_lookup failed: %s\n", strerror(errno));
		return;
	}
	if (val == 0) {
		fprintf(stderr, "failed: map #%d returns value 0\n", map_id);
		return;
	}
	printf("name=%s, var=%d\n",name,val);
	val = 0;
	if (bpf_map_update_elem(map_id, &key, &val, BPF_ANY) != 0) {
		fprintf(stderr, "map_update failed: %s\n", strerror(errno));
		return;
	}
}

static int test(char *filename, int num_progs)
{
	int map0_fds[num_progs], map1_fds[num_progs], fd, i, j = 0;
	struct bpf_link *links[num_progs * 4];
	struct bpf_object *objs[num_progs];
	struct bpf_program *prog;

	for (i = 0; i < num_progs; i++) {
		objs[i] = bpf_object__open_file(filename, NULL);
		if (libbpf_get_error(objs[i])) {
			fprintf(stderr, "opening BPF object file failed\n");
			objs[i] = NULL;
			goto cleanup;
		}

		/* load BPF program */
		if (bpf_object__load(objs[i])) {
			fprintf(stderr, "loading BPF object file failed\n");
			goto cleanup;
		}

		map0_fds[i] = bpf_object__find_map_fd_by_name(objs[i],
							      "enter_open_map");
		map1_fds[i] = bpf_object__find_map_fd_by_name(objs[i],
							      "exit_open_map");
		if (map0_fds[i] < 0 || map1_fds[i] < 0) {
			fprintf(stderr, "finding a map in obj file failed\n");
			goto cleanup;
		}

		bpf_object__for_each_program(prog, objs[i]) {
			links[j] = bpf_program__attach(prog);
			if (libbpf_get_error(links[j])) {
				fprintf(stderr, "bpf_program__attach failed\n");
				links[j] = NULL;
				goto cleanup;
			}
			j++;
		}
		printf("prog #%d: map ids %d %d\n", i, map0_fds[i], map1_fds[i]);
	}

	/* current load_bpf_file has perf_event_open default pid = -1
	 * and cpu = 0, which permits attached bpf execution on
	 * all cpus for all pid's. bpf program execution ignores
	 * cpu affinity.
	 */
	/* trigger some "open" operations */
	fd = open(filename, O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "open failed: %s\n", strerror(errno));
		return 1;
	}
	close(fd);

	/* verify the map */
	for (i = 0; i < num_progs; i++) {
		verify_map(map0_fds[i],"enter_open_map");
		verify_map(map1_fds[i],"exit_open_map");
	}

cleanup:
	for (j--; j >= 0; j--)
		bpf_link__destroy(links[j]);

	for (i--; i >= 0; i--)
		bpf_object__close(objs[i]);
	return 0;
}

int main(int argc, char **argv)
{
	struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY};
	int opt, num_progs = 1;
	char filename[256];

	while ((opt = getopt(argc, argv, "i:h")) != -1) {
		switch (opt) {
		case 'i':
			num_progs = atoi(optarg);
			break;
		case 'h':
		default:
			usage(argv[0]);
			return 0;
		}
	}

	setrlimit(RLIMIT_MEMLOCK, &r);
	snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);

	return test(filename, num_progs);
}

<think>好的,我现在要处理用户关于BFP选路规则实现方案和算法的查询。首先,我需要确认用户提到的BFP到底指的是什么。根据提供的引用内容,用户可能混淆了BFP和BGP。在引用[3]中,BFP被错误地列为第十章中的动态选路协议之一,但实际上正确的术语应该是BGP(边界网关协议)。此外,引用[1]提到的BFP模块是关于特征金字塔平衡的计算机视觉模块,与选路无关。而引用[2]和[3]讨论了BGP作为路径矢量协议的特性。因此,用户可能误将BGP写成了BFP,需要确认这一点。 接下来,我需要检查用户提供的引用内容中是否有关于BFP选路的正确信息。引用[2]详细解释了BGP作为无类别的路径矢量型协议,其工作原理和与距离矢量协议的区别。引用[3]中虽然误写为BFP,但上下文提到的动态选路协议如RIP、OSPF、BGP,所以这里的BFP应为BGP。因此,用户的问题实际上可能与BGP选路规则相关,而非不存在的BFP协议。 接下来,我需要根据正确的协议BGP来整理选路规则和实现方案。BGP的选路规则通常涉及属性比较,如权重、本地优先级、AS路径长度、起源类型等。实现这些规则需要算法来处理路径属性的优先级排序,并结合路由策略进行决策。引用[2]提到BGP不需要计算路由,而是传递IGP计算的路由信息,这影响了其实现方式,可能涉及路由表的管理和路径属性的比较算法。 此外,引用[5]中关于IP分片和ICMP的内容可能与BGP无直接关联,但需要确保在回答时不涉及无关信息。用户可能需要了解BGP如何选择最佳路径的具体步骤和算法,例如路径属性的优先级排序,以及如何在不同AS之间传播路由信息。 最后,生成相关问题时要围绕BGP的选路规则、实现技术、优化方法以及常见问题,确保问题与用户可能的后续疑问相关,并引导进一步的学习。需要避免提及BFP模块或其他不相关的内容,保持焦点在路由协议上。</think>根据引用内容综合分析,BFP在选路协议领域可能存在术语混淆。根据引用[2][3]显示,实际应为**BGP(边界网关协议)**的选路规则实现方案。以下是BGP选路规则的技术解析: --- ### 一、BGP选路规则核心算法 BGP采用**路径矢量算法**,通过比较多个路径属性确定最优路由,核心步骤如下: 1. **权重属性优先**:本地路由器设置的权重值最高优先级(Cisco私有属性)[^2] 2. **本地优先级比较**:用于同一AS内不同出口的优先级排序 3. **最短AS路径原则**:选择经过自治系统最少的路径,计算公式: $$ \text{优选路径} = \arg\min_{p \in Paths} (AS\_PATH\_LENGTH(p)) $$ 4. **起源类型判定**:IGP > EGP > Incomplete[^3] --- ### 二、典型实现方案 基于引用[2][3][5]的技术细节,现代路由器实现BGP选路包含以下模块: | 模块 | 功能 | 实现方法 | |------|------|----------| | 路径属性解析器 | 解析UPDATE报文中的路径属性 | TLV结构解码 | | 决策进程 | 执行选路算法 | 多级属性比较树 | | 路由策略引擎 | 应用路由映射规则 | 正则表达式匹配AS_PATH | | 路由振荡抑制 | 防止路由频繁变更 | 基于时间戳的阻尼算法 | 代码示例(模拟决策逻辑): ```python def bgp_decision(paths): # 按权重降序排序 paths.sort(key=lambda x: x['weight'], reverse=True) # 相同权重时比较本地优先级 paths.sort(key=lambda x: x['local_pref'], reverse=True) # 相同本地优先级时比较AS路径长度 paths.sort(key=lambda x: len(x['as_path'])) return paths[0] ``` --- ### 三、关键技术挑战 1. **路由聚合优化**:通过CIDR实现无类别路由聚合 2. **路径属性验证**:使用MP_REACH_NLRI属性验证可达性[^5] 3. **策略冲突处理**:采用软重配(soft-reconfiguration)保证策略一致性[^2] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值