eBPF编程

简介

如果读取数据包时eBPF程序想要读取超过数据包边界的内容,eBPF程序将会被停止执行。

硬件架构

寄存器

* R0       - return value from in-kernel function, and exit value for eBPF program

* R1 - R5  - arguments from eBPF program to in-kernel function

* R6 - R9  - callee saved registers that in-kernel function will preserve

* R10      - read-only frame pointer to access stack

R1-R5是函数调用的参数寄存器,每次调用了之后这几个寄存器的值可能被改变,所以需要每次调用其他函数后都要重新填充。这5个寄存器被映射到对应平台的实际的参数寄存器,在x86-64下,寄存器的映射方法如下:

R0 - rax

R1 - rdi

R2 - rsi

R3 - rdx

R4 - rcx

R5 - r8

R6 - rbx

R7 - r13

R8 - r14

R9 - r15

R10 – rbp

因为x86-64规定rdi, rsi,rdx, rcx, r8, r9用来做参数传递,rbx, r12 - r15用来做调用者保留。

R6-R9会在eBPF调用其他函数前后保持一致,所以可以用来放eBPF变量。

函数调用

一个eBPF函数被调用的时候会自动带一个参数ctx传递给eBPF程序,放在R1里,(在__bpf_prog_run()函数中实现),这个ctx对于用作filter的eBPF程序来说是skb,对于用作seccomp来说是seccomp_data。所以可以看出,一个使用xt_bpf模块的eBPF过滤程序的原理是在约定的hook点,eBPF被调用,skb被作为第一个参数传进eBPF程序,执行完毕,返回值R0作为判断这个包处理结果的返回值(是否丢弃等)。

指令编码

指令类型类型

在指令编码上,使用8位进行编码,针对不同的指令,这8位的使用情况是不同的,但LSB的最后3位都是用来存储指令类型的。指令主要有如下几种类型:

BPF_LD   0x00

BPF_LDX  0x01

BPF_ST   0x02

BPF_STX  0x03

BPF_ALU  0x04

BPF_JMP  0x05

BPF_ALU64 0x07

以上是表示指令编码的最后3位。BPF_JMP是跳转类型的指令,目前有10个。BPF_ALU和BPF_ALU64是运算类指令,目前有14个。而剩下的则是加载与存储类型的指令。也就是说eBPF一共有3大类指令:跳转、运算、加载与存储。

跳转:BPF_JMP

当最后3位是BPF_ALU或BPF_JMP时,8位的指令编码如上,中间一位有两种取值,表示这个指令使用的源寄存器:

可以看出这一位如果是BPF_X(0),使用src_reg作为源寄存器,如果是BPF_K(1),则使用32位的立即数作为源寄存器。

最后3位是BPF_JMP时,操作符包括:

BPF_JA    0x00

BPF_JEQ   0x10

BPF_JGT   0x20

BPF_JGE   0x30

BPF_JSET  0x40

BPF_JNE   0x50  /* eBPF only: jump != */

BPF_JSGT  0x60  /* eBPF only: signed ‘>‘ */

BPF_JSGE  0x70  /* eBPF only: signed ‘>=‘ */

BPF_CALL  0x80  /* eBPF only: function call */

BPF_EXIT  0x90  /* eBPF only: function return */

运算:BPF_ALU和BPF_ALU64

当最后3位是BPF_ALU或者是BPF_ALU64时:

BPF_ADD   0x00

BPF_SUB   0x10

BPF_MUL   0x20

BPF_DIV   0x30

BPF_OR    0x40

BPF_AND   0x50

BPF_LSH   0x60

BPF_RSH   0x70

BPF_NEG   0x80

BPF_MOD   0x90

BPF_XOR   0xa0

BPF_MOV   0xb0  /* eBPF only: mov reg to reg */

BPF_ARSH  0xc0  /* eBPF only: sign extending shift right */

BPF_END   0xd0  /* eBPF only: endianness conversion */

举例

BPF_XOR | BPF_K| BPF_ALU 意味着:src_reg = src_reg ^ imm32

BPF_MOV | BPF_X| BPF_ALU :将src_reg的值移动到dst_reg

BPF_ADD | BPF_X| BPF_ALU64 :dst_reg = dst_reg + src_reg

加载与存储

加载与存储指令也有多个,每个可以操作的数据的大小是不一样的,这个大小的区别在中间2个位:

BPF_W   0x00:4个字节

BPF_H  0x08 :2个字节

BPF_B  0x10  :1个字节

BPF_DW 0x18  :8个字节

前3个位的mode包括:

BPF_IMM 0x00  /* used for 32-bit mov inclassic BPF and 64-bit in eBPF */

BPF_ABS  0x20

BPF_IND  0x40

BPF_MEM  0x60

BPF_LEN  0x80  /* classic BPF only, reserved in eBPF */

BPF_MSH  0xa0  /* classic BPF only, reserved in eBPF */

BPF_XADD 0xc0  /* eBPF only,exclusive add */

其中BPF_ABS和BPF_IND只能用在数据包处理上,在这时,R6里面是输入数据包sk_buff,R0是输出数据包。

bpf_mov R6, R1 /* save ctx */

bpf_mov R2, 2

bpf_mov R3, 3

bpf_mov R4, 4

bpf_mov R5, 5

bpf_call foo

bpf_mov R7, R0 /* save foo() return value */

bpf_mov R1, R6 /* restore ctx for next call */

bpf_mov R2, 6

bpf_mov R3, 7

bpf_mov R4, 8

bpf_mov R5, 9

bpf_call bar

bpf_add R0, R7

bpf_exit

翻译成x86-64是:

push %rbp

mov %rsp,%rbp

sub $0x228,%rsp

mov %rbx,-0x228(%rbp)

mov%r13,-0x220(%rbp)

mov %rdi,%rbx

mov $0x2,%esi

mov $0x3,%edx

mov $0x4,%ecx

mov $0x5,%r8d

callq foo

mov %rax,%r13

mov %rbx,%rdi

mov $0x2,%esi

mov $0x3,%edx

mov $0x4,%ecx

mov $0x5,%r8d

callq bar

add %r13,%rax

mov -0x228(%rbp),%rbx

mov -0x220(%rbp),%r13

leaveq

retq

在c就是:

u64 bpf_filter(u64 ctx)

{

return foo(ctx, 2, 3, 4, 5) + bar(ctx,6, 7, 8, 9);

}

可行的bpf开发方法:

使用tcpdump、使用iptables

iptables -A INPUT \

-p udp --dport 53 \

-m bpf --bytecode "14,0 0 0 20,177 0 0 0,12 0 0 0,7 0 0 0,64 0 00,21 0 7 124090465,64 0 0 4,21 0 5 1836084325,64 0 0 8,21 0 3 56848237,80 0 012,21 0 1 0,6 0 0 1,6 0 0 0," \

-j DROP

llvm、内核提供的编译器、iovisitor的uBPF编译器

BPF CompilerCollection (BCC)这个工具集包含很多用来观测内核性能的工具,全部使用eBPF,并且提供了python的外部编程能力。其也是使用llvm用作底层编译器,并且整合了llvm中对bpf支持的最新进展。但是要求内核支持版本是4.1。

使用llvm编译并插入eBPF

使用llvm,使用llvm可以使用如下命令编译:

clang-3.7 -O2 -target bpf -c sockex1_kern.c-o soc1.o -I/lib/modules/3.19.0-15-generic/build/include-I/lib/modules/3.19.0-15-generic/build/arch/x86/include/uapi/

这样可以编译一个包含了map和eBPF代码的elf文件。然而这个文件并不是用来直接插入内核的eBPf程序代码,它只是一个包含了需要插入内核的各种信息的一个集合体,内核代码在sample/bpf/里面有提供解析这个文件的代码逻辑,可以自动实现解析和插入内核。

但是这种有个缺点,这种都是使用的bpf系统调用进行插入的eBPF代码,然而这个系统调用只支持插入到特定的内核位置:

BPF_PROG_TYPE_SOCKET_FILTER,    //附在某一个socket上,只对某一个socket产生影响。

BPF_PROG_TYPE_KPROBE,          //附在kprobe上

BPF_PROG_TYPE_SCHED_CLS,       //附在cls_bpf分类模块上

BPF_PROG_TYPE_SCHED_ACT,       //附在act_bpf模块上

可以看到,无法插入到我们希望的hook点上(因为hook点是netfilter的基础设施,netfilter使用xt_bpf来支持bpf,所以就没有在系统调用层级支持)。而类型BPF_PROG_TYPE_SOCKET_FILTER的bpf,即使是使用raw,得到的数据也只是一份拷贝。这就注定了这个机制只能用来分析,不能用来做过滤。所以,目前这条路不通,过滤只能采用xt_bpf。

使用xt_bpf

这是iptables的扩展,可以使用-m参数传递bpf代码进内核。我们可以使用内核提供的bpf编译程序编译代码,或者是tcpdump –ddd,或者是iptables的nfbpf_compile.c程序。但是这三个编译得到的都是cBPF代码。iptables的用户端程序也只支持cBPF代码。所以使用xt_bpf模块的合理选择应该是使用cBPF编程。

如果要使用eBPF编程,可行的方法是在自己实现钩子函数中执行eBPF,或者是复用在xt_bpf的代码。

时间: 2024-08-26 19:52:19

eBPF编程的相关文章

Linux内核project导论——网络:Filter(LSF、BPF、eBPF)

概览 LSF(Linux socket filter)起源于BPF(Berkeley Packet Filter).基础从架构一致.但使用更简单.LSF内部的BPF最早是cBPF(classic).后来x86平台首先切换到eBPF(extended).但因为非常多上层应用程序仍然使用cBPF(tcpdump.iptables),而且eBPF还没有支持非常多平台,所以内核提供了从cBPF向eBPF转换的逻辑,而且eBPF在设计的时候也是沿用了非常多cBPF的指令编码. 可是在指令集合寄存器.还有架

异常笔记--java编程思想

开一个新的系列,主要记一些琐碎的重要的知识点,把书读薄才是目的...特点: 代码少,概念多... 1. 基本概念 异常是在当前环境下无法获得必要的信息来解决这个问题,所以就需要从当前环境跳出,就是抛出异常.抛出异常后发生的几件事: 1.在堆上创建异常对象. 2.当前的执行路径中止                                          3. 当前环境抛出异常对象的引用.                                         4. 异常处理机制接

iOS开发——网络编程OC篇&Socket编程

Socket编程 一.网络各个协议:TCP/IP.SOCKET.HTTP等 网络七层由下往上分别为物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. 其中物理层.数据链路层和网络层通常被称作媒体层,是网络工程师所研究的对象: 传输层.会话层.表示层和应用层则被称作主机层,是用户所面向和关心的内容. http协议   对应于应用层 tcp协议    对应于传输层 ip协议     对应于网络层 三者本质上没有可比性.  何况HTTP协议是基于TCP连接的. TCP/IP是传输层协议,主要

C#网络编程技术FastSocket实战项目演练

一.FastSocket课程介绍 .NET框架虽然微软提供了socket通信的类库,但是还有很多事情要自己处理,比如TCP协议需要处理分包.组包.粘包.维护连接列表等,UDP协议需要处理丢包.乱序,而且对于多连接并发,还要自己处理多线程等等.本期分享课程阿笨给大家带来的是来源于github开源Socket通信中间件:FastSocket,目的就是把大家从繁琐的网络编程技术中彻底地解放和释放出来. 阿笨只想安安静静的学习下网络编程技术Socket后,将学习的成果直接灵活的运用到自己的实际项目中去.

轻松学习C语言编程的秘诀:总结+灵感

目前在准备一套C语言的学习教程,所以我这里就以C语言编程的学习来讲.注意,讲的是"轻松学习",那种不注重方法,拼命玩命的方式也有其效果,但不是我提倡的.我讲究的是在方式方法对头.适合你.减轻你学习负担和心里压力的前提下,才适当的抓紧时间. 因此,探索一种很好的学习方法就是我所研究的主要内容. 众所周知,学习C语言并非易事,要学好它更是难上加难.这和你期末考试背会几个题目的答案考上满分没多大关系,也就是说你考试满分也说明不了你学好.学精通了C语言.那么怎么才算学精通C语言?闭着眼睛对自己

《Java编程思想》第十三章 字符串

<Java编程思想>读书笔记 1.String作为方法的参数时,会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置,从未动过. 2.显式地创建StringBuilder允许预先为他指定大小.如果知道字符串多长,可以预先指定StringBuilder的大小避免多次重新分配的冲突. 1 /** 2 * @author zlz099: 3 * @version 创建时间:2017年9月1日 下午4:03:59 4 */ 5 public class UsingStringBuilder {

Linux Socket编程-(转自吴秦(Tyler))

"一切皆Socket!" 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. --有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时,浏览器的进程怎么与web服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠socket?那什么是socket?socket的类型有哪些?还有socket的基本函数,这些都是本文想介绍的.本文的主要内容如下: 1.网络中进程之间如何通信?

团队编程项目作业

团队名称: 简单 队长 学号:2015035107224 姓名:张志鹏 成员 学号:2015035107071 姓名:邱阳阳 学号:2015035107044 姓名:刘孝东 学号:2015035107007 姓名:孙弘原 学号:2015035107005 姓名:刘文帅 学号:2015035107009 姓名:杨琳 团队编程项目作业名称:爬取豆瓣电影TOP250 选择该团队编程项目原因:感觉项目有意思

团队项目编程作业

团队名称: 简单 队长 学号:2015035107224 姓名:张志鹏 成员 学号:2015035107071 姓名:邱阳阳 学号:2015035107044 姓名:刘孝东 学号:2015035107007 姓名:孙弘原 学号:2015035107005 姓名:刘文帅 学号:2015035107009 姓名:杨琳 团队编程项目作业名称:爬取豆瓣电影TOP250 选择该团队编程项目原因:我爱学习