cilium原理之ebpf尾调用与trace

背景

在深入剖析cilium原理之前,有两个关于epbf的基础内容需要先详细介绍一下:

1. ebpf尾调用

尾调用类似于程序之间的相互跳转,但它的功能更加强大。

2. trace

虽然之前使用trace_printk输出日志,但这个函数不能多用,会有性能问题。而且它的输出可读性差,不利于程序进行分析。本文将着重讲讲如何进行分析,方便程序后续跟踪。

1.ebpf尾调用介绍

尾调用能够从一个程序调到另一个程序,提供了在运行时(runtime)原子地改变程序行为的灵活性。为了选择要跳转到哪个程序,尾调用使用了程序数组map( BPF_MAP_TYPE_PROG_ARRAY),将map及其索引(index)传递给将要跳转到的程序。跳转动作一旦完成,就没有办法返回到原来的程序;但如果给定的map索引中没有程序(无法跳转),执行会继续在原来的程序中执行。

特殊在于,由于函数的调用是由map来保存的,可以使用TC更新map对应的函数为新的函数,实现ebpf程序的局部更新。当然,这就对功能编码也会有更高的要求,需要明确更新可能的影响范围。因为ebpf的单个程序大小是有限制的,而尾调用可以绕开这种限制。

以下是摘自cilium官方文档中的样例:(官方样例无法编译,下面摘取的内容经过作者调整)

#include <bits/types.h>
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/pkt_cls.h>
#include <linux/tcp.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>typedef __uint32_t uint32_t;#define PIN_GLOBAL_NS           2struct bpf_elf_map {__u32 type;__u32 size_key;__u32 size_value;__u32 max_elem;__u32 flags;__u32 id;__u32 pinning;
};#ifndef __stringify
# define __stringify(X)   #X
#endif#ifndef __section
# define __section(NAME)                  \__attribute__((section(NAME), used))
#endif#ifndef __section_tail
# define __section_tail(ID, KEY)          \__section(__stringify(ID) "/" __stringify(KEY))
#endif#ifndef BPF_FUNC
# define BPF_FUNC(NAME, ...)              \(*NAME)(__VA_ARGS__) = (void *)BPF_FUNC_##NAME
#endif#define BPF_JMP_MAP_ID   1static int (*bpf_trace_printk)(const char *fmt, int fmt_size,...) = (void *)BPF_FUNC_trace_printk;#define printk(fmt, ...)                                                 \do {                                                                         \char _fmt[] = fmt;                                                         \bpf_trace_printk(_fmt, sizeof(_fmt), ##__VA_ARGS__);                       \} while (0)static void BPF_FUNC(tail_call, struct __sk_buff *skb, void *map,uint32_t index);struct bpf_elf_map jmp_map __section("maps") = {.type           = BPF_MAP_TYPE_PROG_ARRAY,.id             = BPF_JMP_MAP_ID,.size_key       = sizeof(uint32_t),.size_value     = sizeof(uint32_t),.pinning        = PIN_GLOBAL_NS,.max_elem       = 1,
};__section_tail(BPF_JMP_MAP_ID, 0)
int looper(struct __sk_buff *skb)
{printk("skb cb: %u\n", skb->cb[0]++);tail_call(skb, &jmp_map, 0);printk("skb end looper: cb: %u\n", skb->cb[0]);return TC_ACT_OK;
}__section("prog")
int entry(struct __sk_buff *skb)
{skb->cb[0] = 0;tail_call(skb, &jmp_map, 0);printk("skb end: cb: %u\n", skb->cb[0]);return TC_ACT_OK;
}char __license[] __section("license") = "GPL";

官方代码很清晰,需要注意的是:定义函数时,__section_tail(BPF_JMP_MAP_ID, 0)使用的是BPF_JMP_MAP_ID;而在bpf代码中,调用的时候使用的是tail_call(skb, &jmp_map, 0);中的jmp_map。而这两者之间的关系,则是在定义jmp_map时指定。

功能测试演示


# 将上面文件保存为tail.c
# build
clang -g -c -O2 -target bpf -c tail.c -o tail.o# load
tc qdisc add dev enp1s0 ingress
tc filter replace dev enp1s0 ingress prio 1 handle 1 bpf da obj tail.o sec prog verbose# cat log
cat /sys/kernel/debug/tracing/trace_pipe

虽然上面的程序是死循环,但测试后发现,程序不是真的死循环,会在looper中执行34次后结束。验证函数热更新功能。

<idle>-0     [007] ..s. 443032.404122: 0: skb cb: 29<idle>-0     [007] ..s. 443032.404122: 0: skb cb: 30<idle>-0     [007] ..s. 443032.404123: 0: skb cb: 31<idle>-0     [007] ..s. 443032.404123: 0: skb cb: 32<idle>-0     [007] ..s. 443032.404123: 0: skb cb: 33<idle>-0     [007] ..s. 443032.404124: 0: skb end looper: cb: 34<idle>-0     [007] ..s. 443032.404146: 0: skb cb: 0<idle>-0     [007] ..s. 443032.404148: 0: skb cb: 1<idle>-0     [007] ..s. 443032.404149: 0: skb cb: 2<idle>-0     [007] ..s. 443032.404149: 0: skb cb: 3<idle>-0     [007] ..s. 443032.404150: 0: skb cb: 4

在之前的源代码中,添加如下函数:


__section("newloop")
int newlooper(struct __sk_buff *skb)
{printk("skb end in new looper: cb: %u\n", skb->cb[0]);return TC_ACT_OK;
}

# build
clang -g -c -O2 -target bpf -c tail.c -o tail.o# update func
tc exec bpf graft m:globals/jmp_map key 0 obj tail.o sec newloop
# 特别注意:由于centos8自带的tc版本太低,无法更新,需要使用高版本的tc.可使用cilium自带的tc
# docker run -it --name=mytest --network=host --privileged -v $PWD:/hosts/ -v /sys/fs/bpf:/sys/fs/bpf -v /run/cilium/cgroupv2/:/run/cilium/cgroupv2 cilium:v1.12.7 bash 进入容器后,执行上面的命令# 结果检查
cat /sys/kernel/debug/tracing/trace_pipe

2. send_trace_notify 跟踪

send_trace_notify –> ctx_event_output –> skb_event_outputevent_output


// 调用样例
send_trace_notify(ctx, TRACE_FROM_LXC, SECLABEL, 0, 0, 0,TRACE_REASON_UNKNOWN, TRACE_PAYLOAD_LEN);// 函数定义
send_trace_notify(struct __ctx_buff *ctx, enum trace_point obs_point,__u32 src, __u32 dst, __u16 dst_id, __u32 ifindex,enum trace_reason reason, __u32 monitor)msg = (typeof(msg)) {__notify_common_hdr(CILIUM_NOTIFY_TRACE, obs_point),  // 事件大类:CILIUM_NOTIFY_TRACE,子类:obs_point__notify_pktcap_hdr(ctx_len, (__u16)cap_len),.src_label  = src,   // 源对象id  cilium identity list, identity 全局唯一.dst_label  = dst,   // 目的对象id.dst_id    = dst_id, // 不同的子类,id取值不同.reason    = reason,  // 原因标识.ifindex  = ifindex, // 不同的子类,取值类型不同};ctx_event_output(ctx, &EVENTS_MAP,(cap_len << 32) | BPF_F_CURRENT_CPU,&msg, sizeof(msg));

上报的信息,就是harbor展示的数据来源。

信息说明    

  1. 发送的信息,会放在ring buffer中。如果未读取,会进行覆盖写入。

  2. 这个buffer是cpu级别的,每个cpu中都会有一个独立的。

  3. 读取时,需要指定读取哪个cpu,-1表示读取所有cpu中的信息。

  4. 读取event的具体细节,可参考:

    https://man7.org/linux/manpages/man2/perf_event_open.2.html

下面为cilium使用go实现了perf读取库的使用说明:"github.com/cilium/ebpf/perf"

cilium目前的信息结构说明:

cilium monitor可以读取perf信息,并且能基于identity进行过滤,具体使用可参考该命令说明。


var err errorpath := oldBPF.MapPath(signalmap.MapName)
signalMap, err := ebpf.LoadPinnedMap(path, nil)
if err != nil {log.WithError(err).Warningf("Failed to open signals map")return
}
events, err = perf.NewReader(signalMap, os.Getpagesize())
if err != nil {log.WithError(err).Warningf("Cannot open %s map! Ignoring signals!",signalmap.MapName)return
}go func() {log.Info("Datapath signal listener running")for {record, err := events.Read()switch {case err != nil:signalCollectMetrics(nil, "error")log.WithError(err).WithFields(logrus.Fields{logfields.BPFMapName: signalmap.MapName,}).Errorf("failed to read event")case record.LostSamples > 0:signalCollectMetrics(nil, "lost")default:signalReceive(&record)}}
}()

消息结构如下:

总体分两大类:

  1. lost count,用于记数:数据包丢弃数。

  2. 详细报文说明。

各种sample数据格式说明。

cilium monitor使用样例。


# 使用cilium monitor查看网络策略执行情况:
# remoteID 表示针对对象
# ingress/egress表示入与出流量
# action表示策略结果
cilium monitor -t policy-verdict 
Listening for events on 8 CPUs with 64x4096 of shared memory
Press Ctrl-C to quit
level=info msg="Initializing dissection cache..." subsys=monitor
Policy verdict log: flow 0x95e0951 local EP ID 2467, remote ID kube-apiserver, proto 6, ingress, action allow, auth: disabled, match L3-L4, 172.18.0.6:46866 -> 172.18.0.8:2380 tcp SYN
Policy verdict log: flow 0x580738e1 local EP ID 2467, remote ID 16777218, proto 6, ingress, action allow, auth: disabled, match L3-L4, 172.18.0.2:40122 -> 172.18.0.8:6443 tcp SYN
Policy verdict log: flow 0x4dfe98a0 local EP ID 2467, remote ID kube-apiserver, proto 6, ingress, action allow, auth: disabled, match L3-L4, 172.18.0.4:52314 -> 172.18.0.8:2380 tcp SYN
Policy verdict log: flow 0xb3af0a31 local EP ID 2467, remote ID kube-apiserver, proto 6, egress, action allow, auth: disabled, match L3-L4, 172.18.0.8:42736 -> 172.18.0.6:2380 tcp SYN
Policy verdict log: flow 0xfdb19557 local EP ID 2467, remote ID kube-apiserver, proto 6, egress, action allow, auth: disabled, match L3-L4, 172.18.0.8:46246 -> 172.18.0.4:2380 tcp SYN
Policy verdict log: flow 0x5438a30a local EP ID 2467, remote ID 16777218, proto 6, ingress, action allow, auth: disabled, match L3-L4, 172.18.0.2:40164 -> 172.18.0.8:6443 tcp SYN

endpoint id,可通过这个方式查到。

展望

跟踪只允许基于endpointID进行跟踪,无法基于ip、端口等信息,当网络流量在多个节点中流转时,无法有效的进行跟踪。

这一块可基于数据分析组件来实现,它可以导出json结构数据,采集统一汇总分析统计。

1.访问入口与目标pod在同节点上,ip nat情况:

2.访问入口与目标pod在同节点上,流量跟踪情况:

可以看到,源端口在网络流转中是不变的,可以基于此作为跟踪线索。

可基于es+kibana,将cilium trace相关数据统一在es中存储,并基于kibana展示,如下图所示:

红色框分别为源ip、目标ip、源端口、目标端口。而特殊的flb_host_ip是坐着在采集时添加的节点主机的ip,用来分析不同主机间的流量。

作者:沃趣科技产品研发部

更多技术干货请关注公号【云原生数据库

squids.cn,云数据库RDS,迁移工具DBMotion,云备份DBTwin等数据库生态工具。

irds.cn,多数据库管理平台(私有云)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/239952.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用StableDiffusion进行图片Inpainting原理

论文链接&#xff1a;RePaint: Inpainting using Denoising Diffusion Probabilistic Models代码链接&#xff1a;RePaint Inpainting任务是指在任意一个二进制的掩码指定的图片区域上重新生成新的内容&#xff0c;且新生成的内容需要和周围内容保持协调。当前SOTA模型用单一类…

高级算法设计与分析(四) -- 贪心算法

系列文章目录 高级算法设计与分析&#xff08;一&#xff09; -- 算法引论 高级算法设计与分析&#xff08;二&#xff09; -- 递归与分治策略 高级算法设计与分析&#xff08;三&#xff09; -- 动态规划 高级算法设计与分析&#xff08;四&#xff09; -- 贪心算法 高级…

FATFS文件系统

文件系统是为了存储和管理数据&#xff0c;而在存储设备上建立的一种组织结构。 Windows常用的文件系统&#xff1a; 1、FAT12 2、FAT16 3、FAT32 4、exFAT 5、NTFS FAT&#xff1a;File Alloction Table 文件分配表 在小型的嵌入式存储设备大多…

Ubuntu 常用命令之 ping 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 ping命令是一种网络诊断工具&#xff0c;用于测试主机之间网络的连通性。它发送ICMP Echo Request消息到指定的网络主机&#xff0c;并等待接收ICMP Echo Reply。通过这种方式&#xff0c;我们可以知道两台主机之间的网络是否畅通…

pycharm修改项目文件夹名称

目录 1 修改项目文件夹名称 2 修改代码中的项目名称 1 修改项目文件夹名称 选中项目文件夹&#xff0c;右键&#xff0c;选择refactor-rename。 选择rename project&#xff1a; 然后输入新的项目名称。 此时进入资源管理器&#xff0c;修改项目文件夹的名字&#xff0c;完成…

IntelliJ IDEA 2023.3 新功能介绍

IntelliJ IDEA 2023.3 在众多领域进行了全面的改进&#xff0c;引入了许多令人期待的功能和增强体验。以下是该版本的一些关键亮点&#xff1a; IntelliJ IDEA mac版下载 macappbox.com/a/intellij-idea-for-mac.html 1. AI Assistant 的全面推出 IntelliJ IDEA 2023.3 中&am…

ES-mapping

类似数据库中的表结构定义&#xff0c;主要作用如下 定义Index下的字段名( Field Name) 定义字段的类型&#xff0c;比如数值型、字符串型、布尔型等定义倒排索引相关的配置&#xff0c;比如是否索引、记录 position 等 index_options 用于控制倒排索记录的内容&#xff0c;有如…

钓鱼与木马实践(仅供参考不可实践)

声明:内容仅供学习&#xff0c;请勿违法使用&#xff0c;违者后果自负 一.部署云服务器 购买一台云服务器,Windows&#xff08; 中文 &#xff09;版本即可 华为云官网&#xff1a;https://www.huaweicloud.com/ 登录后进入控制台购买完成后远程登录云服务器 二.部署WEB运行…

QQ群发邮件的技巧?QQ邮箱邮件群发怎么发?

QQ群发邮件怎么设置&#xff1f;QQ邮件群发必备利器有哪些&#xff1f; QQ群发邮件&#xff0c;作为当下最流行的通讯方式之一&#xff0c;已经被广大网友所熟知。但是&#xff0c;要想真正掌握QQ群发邮件的技巧&#xff0c;却不是一件容易的事情。下面&#xff0c;就让蜂邮ED…

【Linux笔记】系统信息

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux学习 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 命令 1. uname - 显示系统信息 2. hostname - 显示或设置系统主机名 3. top - 显示系统资源使用情况 4. df - 显示磁盘空间使用情…

Qt通用属性工具:随心定义,随时可见(一)

一、开胃菜&#xff0c;没图我说个DIAO 先不BB&#xff0c;给大家上个效果图展示下&#xff1a; 上图我们也没干啥&#xff0c;几行代码&#xff1a; #include "widget.h" #include <QApplication> #include <QObject> #include "QtPropertyEdit…

pmp到底是什么?

一、PMP是什么 PMP 是项目管理的入门级证书&#xff0c;全称是项目管理专业人士资格认证&#xff0c;由美国项目管理协会&#xff08;PMI&#xff09;举办的&#xff0c;从1999 年到现在已经有20多年发展历史了。 顾名思义&#xff0c;PMP考试就是一场评估应试者是否具备专业…

React学习计划-React16--React基础(五)脚手架创建项目、todoList案例、配置代理、消息订阅与发布

一、使用脚手架create-react-app创建项目 react脚手架 xxx脚手架&#xff1a;用来帮助程序员快速创建一个基于xxx库的模板项目 包含了所有需要的配置&#xff08;语法检查、jsx编译、devServe…&#xff09;下载好了所有相关的依赖可以直接运行一个简单的效果 react提供了一个…

红队打靶练习:DIGITALWORLD.LOCAL: MERCY V2

目录 信息收集 1、arp 2、netdiscover 3、nmap 4、nikto 5、whatweb 6、总结 目录探测 1、gobuster 2、dirsearch WEB enum4linux枚举工具 smbclient工具 knock工具 CMS 文件包含漏洞 Tomcat 提权 系统信息收集 本地提权 get root 信息收集 1、arp ┌──…

【设计模式】命令模式

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、什么是命令模式&#xff1f; 二、命令模式的优点和应用场景 三、命令模式的要素和实现 3.1 命令 3.2 具体命令 3.3 接受者 …

Github 2023-12-23 开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2023-12-23统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目6C项目2C项目1Jupyter Notebook项目1HTML项目1Go项目1非开发语言项目1 免费API集体清单 创建周期…

VGGNet

目录 一、VGGNet介绍 1、VGG块 2、VGG架构 3、LeNet, AlexNet和VGGNet对比 4、总结 二、代码实现 1、定义VGG卷积块 2、VGG网络 3、训练模型 4、总结 一、VGGNet介绍 VGGNet&#xff08;Visual Geometry Group Network&#xff09;是一种深度卷积神经网络&#xff0c;…

java String转asc码,然后ascII再转四位的16进制数。

理论知识补充&#xff1a; char是Java中的保留字&#xff0c;表示一种数据类型。与别的语言不同的是&#xff0c;char在Java中是16位的&#xff0c;因为Java用的是Unicode编码。不过8位的ASCII码包含在Unicode编码中&#xff0c;其值对应十进制的表示范围是0~127。 char是Java八…

《软件方法(下)》8.2.4 类和属性的命名

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 8.2 建模步骤C-1 识别类和属性 8.2.4 类和属性的命名 8.2.4.2 关于DDD话语中的“通用语言” DDD&#xff08;领域驱动设计&#xff09;话语中有“通用语言&#xff08;Ubiquitous L…

2023-12-22 linux C语言pthread_kill函数,pthread_kill(tid, 0)可以用来判断线程是否存在

一、该函数其实不是kill线程&#xff0c;而是向线程发送一个signal&#xff0c;pthread_kill()函数的作用是向某个线程传递一个信号&#xff0c;创建的线程中signal(SIGKILL,sig_handler)函数去处理对应的信号&#xff0c;如果你给一个线程发送了SIGQUIT、SIGKILL&#xff0c;但…