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.hqwc.cn/news/292042.html

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

相关文章

Fl Studio 20.9 中文汉化破解版永久版下载(含Keygen)

FL Studio 20.9.0.2736破解版是最优秀、最繁荣的数字音频工作站 (DAW) 之一&#xff0c;日新月异。 它是一款录音机和编辑器&#xff0c;可让您不惜一切代价制作精美的音乐作品并保存精彩的活动画廊。 为方便用户&#xff0c;FL Studio Keygen提供三种不同的版本——Fruity 版…

css图片属性,图片自适应

CSS 图片属性指南&#xff1a;background-size 和 object-fit 在前端开发中&#xff0c;使用图片是非常常见的。为了让图片在网页中显示得更好&#xff0c;CSS 提供了多种属性来调整和控制图片的大小和布局。其中&#xff0c;background-size 和 object-fit 是两个常用的属性&a…

邮政物流快递查询,并筛选出其中的提前签收件

批量查询邮政快递单号的物流信息&#xff0c;并将其中的提前签收件筛选出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 邮政快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的朋友记得先注册&#xff…

深⼊理解指针

1. 内存和地址 1.1 内存 在讲内存和地址之前&#xff0c;我们想有个⽣活中的案例&#xff1a; 假设有⼀栋宿舍楼&#xff0c;把你放在楼⾥&#xff0c;楼上有100个房间&#xff0c;但是房间没有编号&#xff0c;你的⼀个朋友来找你玩&#xff0c; 如果想找到你&#xff0c;就…

爬虫课程考试方式说明

爬虫课程考试方式说明 一、开课情况 考查课 082116415 50人&#xff0c;0864211&#xff0c;1-15单周 理论学时16 实验学时0 上课地点&#xff1a;周一 3-4节 十号教学楼A303 51人&#xff0c;0864212&#xff0c;1-15单周 理论学时16 实验学时0 上课地点&#xff1a;周四 3-4…

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

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 date命令在Ubuntu系统中用于显示或设置系统的日期和时间。 date常见的参数 -d, --dateSTRING&#xff1a;显示STRING指定的时间&#xff0c;而不是当前时间。-u, --utc, --universal&#xff1a;显示或设置协调世界时间。-R, --…

协作机器人(Collaborative-Robot)安全碰撞的速度与接触力

协作机器人&#xff08;Collaborative-Robot&#xff09;的安全碰撞速度和接触力是一个非常重要的安全指标。在设计和使用协作机器人时&#xff0c;必须确保其与人类或其他物体的碰撞不会对人员造成伤害。 对于协作机器人的安全碰撞速度&#xff0c;一般会设定一个上限值&…

057:vue组件方法中加载匿名函数

第057个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

mysql主从复制(在虚拟机centos的docker下)

1.安装docker Docker安装(CentOS)简单使用-CSDN博客 2.部署2个mysql docker run --name some-mysql1 -p 33061:3306 -e MYSQL_ROOT_PASSWORD123456 -d mysql:5.7 --character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_cidocker run --name some-mysql2 -p 330…

YACS(上海计算机学会竞赛平台)三星级挑战——牛奶供应(一)

题目描述 有一家牧场每天都会产出牛奶&#xff0c;在第 i 天&#xff0c;牛奶的产量为p[i]​。生产的牛奶可以卖到市场上&#xff0c;在第 i 天&#xff0c;市场需求为 c[i]​。如果市场需求不大&#xff0c;卖不掉牛奶&#xff0c;则多余的牛奶就会放进冷库保存。牛奶有一个保…

PyGame实现打砖块游戏

文章目录 砖块实现小车小球初始化和主循环 打砖块也是一个非常经典的小游戏&#xff0c;玩法大致如下&#xff0c;用一个小车接一个小球&#xff0c;然后反射小球&#xff0c;使之打在砖块上&#xff0c;当小球碰到砖块之后&#xff0c;则砖块被消掉&#xff0c;逻辑十分清晰。…

EDA实验-----直流电机驱动设计(Quartus II )

目录 一、实验目的 二、实验仪器设备 三、实验的重点和难点 四、实验原理 五、实验步骤 六、实验报告 七、实验过程 1.分频器代码 2.方向选择器 3.直流电动机工作原理 4.电路连接图 5.文件烧录 一、实验目的 了解直流电机控制的工作原理和实现的方法。掌握PWM波控…