Linux 4.4 以上内核基于 eBPF 可以将任何内核函数调用转换成可带任何数据的用户空间事件。上回,我们说到bpf_trace_printk 带的参数太多了,会出现error: <unknown>:0:0: in function kprobe__inet_listen i32 (%struct.pt_regs*): too many args to 0x55a83e8f8320: i64 = Constant<6>这样的错误,这是 BPF 的限制。解决这个问题的办法就是使用 perf,它支持传递任意大小的结构体到用户空间。
我们对比原来的代码进行修改,原代码如下:

from bcc import BPF# Hello BPF Program
bpf_text = """
#include <net/inet_sock.h>
#include <bcc/proto.h>
#include <net/sock.h>
// 1. Attach kprobe to "inet_listen"
int kprobe__inet_listen(struct pt_regs *ctx, struct socket *sock, int backlog)
{// cast types. Intermediate cast not needed, kept for readabilitystruct sock *sk = sock->sk;struct inet_sock *inet = (struct inet_sock *)sk;// Create an populate the variableu32 netns = 0;// Read the netns inode number, like /proc doesnetns = sk->__sk_common.skc_net.net->ns.inum;bpf_trace_printk("Listening on %x %d with %d pending connections in container %d \\n", inet->inet_rcv_saddr, inet->inet_sport, backlog, netns);return 0;
};
"""# 2. Build and Inject program
b = BPF(text=bpf_text)# 3. Print debug output
while True:print b.trace_readline()

运行时会出现如下错误:
要使用 perf,我们需要:

  • 定义一个结构体
  • 声明一个事件
  • 推送(push)事件
  • Python 端再定义一遍这个事件(将来这一步就不需要了)
  • 消费并格式化输出事件

为了使内核检测器验证这个程序的内存访问是合法的,我们让内存访问变得更加显式,使用受信任的 bpf_probe_read 函数,可以用它读取任何内存地址。

为了使得程序可以正常运行,bpf_trace_printk 先使用三个参数,只获取IP信息,端口和 backlog 信息,将程序改为:

from bcc import BPF# BPF Program
bpf_text = """
#include <net/sock.h>
#include <net/inet_sock.h>
#include <bcc/proto.h>// Send an event for each IPv4 listen with PID, bound address and port
int kprobe__inet_listen(struct pt_regs *ctx, struct socket *sock, int backlog)
{// Cast types. Intermediate cast not needed, kept for readabilitystruct sock *sk = sock->sk;struct inet_sock *inet = inet_sk(sk);// Working values. You *need* to initialize them to give them "life" on the stack and use them afterwardu32 laddr = 0;u16 lport = 0;// Pull in details. As 'inet_sk' is internally a type cast, we need to use 'bpf_probe_read'// read: load into 'laddr' 'sizeof(laddr)' bytes from address 'inet->inet_rcv_saddr'bpf_probe_read(&laddr, sizeof(laddr), &(inet->inet_rcv_saddr));bpf_probe_read(&lport, sizeof(lport), &(inet->inet_sport));// Push eventbpf_trace_printk("Listening on %x %d with %d pending connections\\n", ntohl(laddr), ntohs(lport), backlog);return 0;
};
"""# Build and Inject BPF
b = BPF(text=bpf_text)# Print debug output
while True:print b.trace_readline()

运行程序,在另一终端使用nc小工具建立单连接,结果如下:
可以看到,我们已经成功地使用bpf_probe_read读取到了相应变量的地址,获取了IP信息,端口和 backlog 信息。接下来我们使用perf,修改程序,使得bpf_trace_printk 带的参数超过三个。

在c程序中加入以下代码:

struct listen_evt_t {u64 laddr;u64 lport;u64 netns;u64 backlog;
};
BPF_PERF_OUTPUT(listen_evt);

kprobe__inet_listen函数中使用以下代码代替bpf_trace_printk

struct listen_evt_t evt = {.laddr = ntohl(laddr),.lport = ntohs(lport),.netns = netns,.backlog = backlog,
};
listen_evt.perf_submit(ctx, &evt, sizeof(evt));

在python中加入以下代码:

# We need ctypes to parse the event structure
import ctypes# Declare data format
class ListenEvt(ctypes.Structure):_fields_ = [("laddr",   ctypes.c_ulonglong),("lport",   ctypes.c_ulonglong),("netns",   ctypes.c_ulonglong),("backlog", ctypes.c_ulonglong),]# Declare event printer
def print_event(cpu, data, size):event = ctypes.cast(data, ctypes.POINTER(ListenEvt)).contentsprint("Listening on %x %d with %d about %d" % (event.laddr,event.lport,event.backlog,event.netns,))

使用以下代码代替python中的循环输出:

b["listen_evt"].open_perf_buffer(print_event)
while True:b.kprobe_poll()

最终修改后的代码如下:

from bcc import BPF# We need ctypes to parse the event structure
import ctypes# Declare data format
class ListenEvt(ctypes.Structure):_fields_ = [("laddr",   ctypes.c_ulonglong),("lport",   ctypes.c_ulonglong),("netns",   ctypes.c_ulonglong),("backlog", ctypes.c_ulonglong),]# Declare event printer
def print_event(cpu, data, size):event = ctypes.cast(data, ctypes.POINTER(ListenEvt)).contentsprint("Listening on %x %d with %d about process %d" % (event.laddr,event.lport,event.backlog,event.netns,))# BPF Program
bpf_text = """
#include <net/sock.h>
#include <net/inet_sock.h>
#include <bcc/proto.h>struct listen_evt_t {u64 laddr;u64 lport;u64 netns;u64 backlog;
};
BPF_PERF_OUTPUT(listen_evt);// Send an event for each IPv4 listen with PID, bound address and port
int kprobe__inet_listen(struct pt_regs *ctx, struct socket *sock, int backlog)
{// Cast types. Intermediate cast not needed, kept for readabilitystruct sock *sk = sock->sk;struct inet_sock *inet = inet_sk(sk);// Working values. You *need* to initialize them to give them "life" on the stack and use them afterwardu32 laddr = 0;u16 lport = 0;// Create an populate the variableu32 netns = 0;// Read the netns inode number, like /proc doesnetns = sk->__sk_common.skc_net.net->ns.inum;// Pull in details. As 'inet_sk' is internally a type cast, we need to use 'bpf_probe_read'// read: load into 'laddr' 'sizeof(laddr)' bytes from address 'inet->inet_rcv_saddr'bpf_probe_read(&laddr, sizeof(laddr), &(inet->inet_rcv_saddr));bpf_probe_read(&lport, sizeof(lport), &(inet->inet_sport));// Push eventstruct listen_evt_t evt = {.laddr = ntohl(laddr),.lport = ntohs(lport),.netns = netns,.backlog = backlog,};listen_evt.perf_submit(ctx, &evt, sizeof(evt));//bpf_trace_printk("Listening on %x %d with %d pending connections\\n", ntohl(laddr), ntohs(lport), backlog);return 0;
};
"""# Build and Inject BPF
b = BPF(text=bpf_text)# Print debug output
b["listen_evt"].open_perf_buffer(print_event)
while True:b.kprobe_poll()

运行程序,在另一终端使用nc小工具建立单连接:

程序结果如下:

可以看到,我们已经成功地使用bpf_probe_read读取到了相应变量的地址,使用perf使得bpf_trace_printk 带了四个参数参数,获取了IP信息,端口、backlog 信息和网络命名空间。

eBPF内核探测中将任意系统调用转换成事件相关推荐

  1. 【引用】在Eclipse中将java Project转换成Dynamic Web Project

    编辑工程的.project文件: 添加 <nature>org.eclipse.wst.common.project.facet.core.nature</nature> &l ...

  2. Java中将inputstream输入流转换成byte[]字节数组

    Java中将inputstream输入流转换成byte[]字节数组 Java中的I/O机制都是基于数据流进行输入和输出的,将流转换成字节数组保存下来是数据流传输必不可少的一部分.转换的代码如下(在具体 ...

  3. oracle转换成字符型,Oracle中将Clob字段转换成字符串

    1. 利用dbms_lob.substr()方法可将对应字段转换成字符串如下 select dbms_lob.substr(content) from NEWS 该方法有个缺点,当content字段长 ...

  4. Oracle任意字符串转换成拼音首字母简写

    Oracle任意字符串转换成拼音首字母简写 需求目标 将"拼音简码"四个字转换成拼音首字母简写PYJM 实现代码 CREATE OR REPLACE FUNCTION FUN_GE ...

  5. c#中将dicom文件格式转换成可读图片

    c#中将dicom文件格式转换成可读图片 不多说,直接上代码 using System; using System.Collections.Generic; using System.IO; usin ...

  6. Python中将字节流文件转换成图片文件

    Python中将字节流文件转换成图片文件 import urllib3 import os #PIL图像处理标准库 from PIL import Image from io import Bytes ...

  7. 任意十六进制数转换成十进制数----不管你输入多长都能转换

    原来它妹这种方法叫大数运算,现在才知道,害我花上好几个小时自己去想--操呀-- 算法由我博客中求阶乘算法变形而得-- 下面是改过后的算法--原算法在后面-- 运行结果: #include<std ...

  8. 【转】vim中将tab自动转换成空格

    在vim中,有时需要将tab转换成space.使用ret命令(replace tab). [range]ret[ab]! [new-tabstop] 举例:将第一行到文件尾的tab转换成space,每 ...

  9. 经纬度换算数值_如何在Excel中将经纬度数值转换成度分秒

    很多时候GPS上边显示地理坐标不是十进制的,而是度分秒形式,必须转换成十进制才能导入Arcgis等作图软件.而有很多网友也在咨询,excel中怎么将经纬度数值转换成度分秒,或者反转将度分秒如何转成数值 ...

最新文章

  1. 如何用c语言读取硬盘串号_如何用C语言实现OOP
  2. linux shell awk 单引号分割
  3. ArcGIS For JavaScript API 默认参数
  4. 快速失败Vs安全失败(Java迭代器附示例)
  5. .NET 客户IP地址捕捉
  6. android app入口函数,Android App程序运行过程 ActivityThread.main()------详解系列(一)...
  7. unity内置浏览器插件UniWebView的使用(支持Android,ios,Mac)
  8. Excel单元格设灰色及锁定
  9. google提供的blog搜索网址
  10. 麻省理工学院公开课:算法导论
  11. 苏格拉底的爱情与婚姻观
  12. 钢铁少女 无限钻石安卓版下载 和 源代码部署成功
  13. 【机器翻译】机器翻译入门
  14. github of gist完全使用指南
  15. 第二阶段--团队冲刺--第四天
  16. AP AUTOSAR 6——Execution Management
  17. 《图解经济学》读后感
  18. Axure仿淘宝搜索原型
  19. C/C++笔试题(13)
  20. 2014年美国数学建模竞赛C题总结

热门文章

  1. 修改及查看mysql数据库的字符集
  2. CEF(包含均衡负载)
  3. SpringBoot___自定义消息转换器、MVC配置
  4. 破解 Windows 2003终端服务许可证
  5. c实现的trim函数
  6. Reboot与init 6的区别
  7. 2006上半年程序员级试题答案
  8. CWNA考试常见RF术语
  9. Java:定时启动线程
  10. python class 初始化顺序