云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

数据库_云服务器参数_

小七 141 0

eBPF、插座、跳距和手动写入eBPF组件

一个朋友给了我一个有趣的任务:从用户空间程序建立的TCP连接中提取ipttl值。这个看似简单的任务很快就演变成了一场史诗般的Linux系统编程黑客。结果代码被严重过度设计了,但是孩子,我们在这个过程中学到了很多东西吗!Paul Miller的CC BY-SA 2.0图像上下文您可能想知道她为什么要检查TTL包字段(在IPv4中正式称为"IP生存时间(TTL)",在IPv6中称为"跳数"(Hop Count)?原因很简单-她想确保连接路由到我们的数据中心之外。"跳距"(由发端机器设置的TTL值与在目的地接收到的数据包中TTL值之间的差值)显示了数据包跨越了多少个路由器。如果一个包跨越两个或多个路由器,我们知道它确实来自我们的数据中心之外。查看TTL值是不常见的(除了当TTL达到零时通过检查来减轻路由循环的目的)。解决这个问题的通常方法是将服务器的IP范围列入黑名单。但在我们的设置中并不是那么简单。我们的IP编号配置相当巴洛克,有大量的选播、单播和保留IP范围。有些属于我们,有些不属于我们。我们希望避免维护IP范围的硬编码阻止列表。这个想法的要点是:我们要从返回的SYN+ACK包中记录TTL值。有了这个数字,我们可以估计跳跃距离-路径上路由器的数量。如果跳跃距离为:零:我们知道连接到本地主机或本地网络。一:连接通过我们的路由器,就在它后面被终止了。二:连接通过两个路由器。很可能是我们的路由器,就在它附近。对于我们的用例,我们想知道跃点距离是两个还是更多-这将确保连接被路由到数据中心之外。不那么容易从用户空间应用程序读取TTL值很容易,对吧?不,事实证明这几乎是不可能的。以下是我们早期考虑的理论选择:A) 运行类似于原始套接字的libpcap/tcpdump,并手动捕获SYN+ACK。我们很快排除了这种设计——它需要更高的特权。而且,原始套接字非常脆弱:如果用户空间应用程序跟不上,它们可能会遭受数据包丢失。B) 使用IP峎RECVTTL套接字选项。IP帴RECVTTL请求将"cmsg"数据附加到recvmsg()系统调用中的控制/辅助数据。对于UDP连接来说,这是一个不错的选择,但TCP SOCK_STREAM套接字不支持此套接字选项。提取TTL并不容易。所以你附加过滤器来统治世界!CC BY-SA 2.0图像由Lee Jordan提供等等,还有第三条路!你看,在相当长的一段时间内,可以将BPF过滤程序附加到套接字上。见插座(7)SO_ATTACH_FILTER(从Linux 2.2开始),SO_ATTACH_BPF(从Linux 3.19开始)附加一个经典的BPF(SO_Attach_FILTER)或扩展的BPF(SO_ATTACH_BPF)程序到套接字以用作传入数据包。如果过滤器gram返回零。如果过滤器程序返回非零小于数据包的数据长度的值,即数据包将被截断为返回的长度。如果值筛选器返回的值大于或等于数据包的数据长度,允许数据包进行unmodi‐菲德。您可能已经利用了SO_ATTACH_FILTER:这就是tcpdump/wireshark在您从线路上转储数据包时进行过滤的方式。它是如何工作的?根据BPF程序的结果,数据包可以被过滤、截断或传递到套接字而不需要修改。通常soattach_FILTER用于原始套接字,但是令人惊讶的是,BPF过滤器也可以附加到普通的SOCK_STREAM和SOCK_DGRAM套接字!但我们不想截短数据包-我们要提取TTL。不幸的是,对于经典的BPF(cBPF),不可能从正在运行的BPF过滤器程序中提取任何数据。eBPF和地图这与现代BPF机械发生了变化,包括:现代化eBPF字节码eBPF地图bpf()系统调用SO_ATTACH_BPF套接字选项eBPF字节码可以看作是对经典BPF的扩展,但是真正让它发光的是额外的特性。gem是"地图"的抽象。eBPF映射是一种允许eBPF程序存储数据并与用户空间代码共享的东西。可以将eBPF映射看作是用户空间程序和运行在内核空间中的eBPF程序之间共享的数据结构(通常是哈希表)。为了解决TTL问题,我们可以使用eBPF过滤程序。它将查看传递数据包的TTL值,并将它们保存在eBPF映射中。稍后,我们可以检查eBPF映射并分析用户空间中记录的值。所以,你要附加你的BPF来统治世界!要使用eBPF,我们需要设置一些东西。首先,我们需要创建一个"eBPF图"。有许多专门的映射类型,但是为了我们的目的,让我们使用"hash"BPF_map_TYPE_hash类型。我们需要计算出"bpf(bpf_MAP_CREATE,MAP type,key size,value size,limit,flags)"参数。对于我们的小TTL程序,让我们设置4个字节的键大小和8个字节的值大小。最大元素限制设置为5。不管怎样,我们希望一个连接中的所有数据包都只有一个一致的TTL值。这就是Golang代码的外观:bpfMapFd,错误:=新地图(ebpf.哈希,4,8,5,0)这里需要一句警告。BPF映射使用"锁定内存"资源。使用多个BPF程序和映射,很容易耗尽默认的64kib限制。考虑一下使用ulimit-l来解决这个问题,例如:乌利米特-l 10240系统调用返回一个指向我们刚刚创建的内核bpf映射的文件描述符。有了它,我们就可以在地图上操作了。操作是可能的:bpf(bpf_MAP_LOOKUP_ELEM,)bpf(bpf地图更新元素,,,)bpf(bpf_MAP_DELETE_ELEM,)bpf(bpf_MAP_GET_NEXT_KEY,)稍后再谈这个。创建映射后,我们需要创建一个BPF程序。与传统的BPF(字节码是SO廑ATTACH_FILTER的参数)不同,字节码现在由BPF()syscall加载。具体地说:bpf(bpf_PROG_LOAD)。在我们的Golang程序中,eBPF程序设置如下:ebpfInss:=ebpf.说明{ebpf.BPFIDstOffSrc公司(ebpf.LdXW公司, ebpf.Reg0, ebpf.Reg1,16),ebpf.BPFIDSTOFFIM公司(杰奇姆, ebpf.Reg0,3,int32(htons(以太网IPV6)),ebpf.bpfidstrc(ebpf.MovSrc公司, ebpf.Reg6, ebpf.Reg1),ebpf.BPFIImm公司(ebpf.LdAbsB公司,int32(-0x100000+8)),...ebpf.BPFIDstImm公司(ebpf.MovImm公司, ebpf.Reg0,-1),ebpf.BPFIOp公司(ebpf.出口),}BPF程序,错误:=ebpf.new程序(ebpf.插座过滤器,&ebpfInss,"GPL",0)手写eBPF是相当有争议的。大多数人使用clang(从3.7版开始)将用C方言编写的代码编译成eBPF字节码。生成的字节码保存在ELF文件中,大多数eBPF库都可以加载该文件。这个ELF文件还包含对贴图的描述,因此不需要手动设置它们。我个人不认为为简单的soattach_BPF片段添加ELF/clang依赖关系有什么意义。不要害怕原始字节码!BPF呼叫约定在我们进一步讨论之前,我们应该强调关于eBPF环境的一些事情。官方内核文档不太友好:文件/网络/过滤器.txt首先要知道的一点是呼叫约定:R0-返回内核函数中的值,以及eBPF程序的退出值R1-R5-从eBPF程序到内核内函数的参数R6-R9-被调用方保存了内核函数中将保留的寄存器R10-指向访问堆栈的只读帧指针当BPF启动时,R1包含一个指向ctx的指针。此数据结构定义为struct\u sk_buff。例如,要访问协议字段,您需要运行:r0=*(u32*)(r1+16)或者换句话说:ebpf.BPFIDstOffSrc公司(ebpf.LdXW公司, ebpf.Reg0, ebpf.Reg1,16),这正是我们在程序的第一行所做的,因为我们需要在IPv4或IPv6代码分支之间进行选择。访问BPF有效载荷接下来,有关于包有效载荷加载的特殊说明。大多数BPF项目(但不是全部!)运行在包过滤的上下文中,因此通过使用用于访问包数据的神奇操作码来加速数据查找是有意义的。与像ctx->data[x]那样取消对上下文的引用来加载字节,BPF支持BPF\LD指令,它可以在一个操作中完成。不过,文件上说,也有一些注意事项:eBPF有两个非通用指令:(BPF|u ABS |BPF u LD)和(BPF_IND || BPF|LD),用于访问数据包数据。它们必须从经典的BPF中继承过来,才能有强大的性能在eBPF解释器中运行的套接字过滤器。这些说明只能当解释器上下文是指向"struct sk buff"的指针时使用,并且有七个隐式操作数。寄存器R6是一个隐式输入,必须包含指向sk_buff的指针。寄存器R0是一个隐式输出,它包含从数据包中获取的数据。寄存器R1-R5是暂存寄存器并且不能用于跨BPF|u ABS | BPF uld或BPF指示。换句话说:在调用BPF之前,我们必须将ctx移到R6,如下所示:ebpf.bpfidstrc(ebpf.MovSrc公司, ebpf.Reg6, ebpf.Reg1),然后我们可以称之为负载:ebpf.BPFIImm公司(ebpf.LdAbsB公司,int32(-0x100000+7)),在这个阶段,结果是r0,但是我们必须记住r1-r5应该被认为是脏的。对于指令来说,BPF函数看起来非常像函数调用。魔法层3偏移接下来注意加载偏移量-我们加载了数据包的-0x100000+7字节。这个神奇的偏移量是另一个BPF上下文好奇心。事实上,在SOCK_STREAM(或SOCK_DGRAM)套接字上的sou ATTACH_BPF下加载的BPF脚本默认情况下只会看到第4层和更高的OSI层。要提取TTL,我们需要访问第3层报头(即IP报头)。为了在L4上下文中访问L3,我们必须用magic-0x100000偏移数据查找。这个神奇常数是在内核中定义的。为了完整起见,+7当然是IPv4数据包中TTL字段的偏移量。我们的小型BPF计划也支持