前言

本文将分享从0开始编写自己的bcc程序。那么开始编写bcc之前,自己一定要明确,我们要用bcc提取什么数据。本文的实例是统计内核网络中的流量,我要提取的数据关键字段为进程的PID,进程的名字,进程的收包实时流量、发包实时流量,收包流量总和,发包流量总和,总的收发流量等。

我们知道bcc是eBPF的一个工具集,是eBPF提取数据的上层封装,它的形式是python中嵌套bpf程序。python部分的作用是为用户提供友好的使用eBPF的上层接口,也用于数据处理。bpf程序会注入内核,提取数据。当bpf程序运行时,通过LLVM将bpf程序编译得到bpf指令集的elf文件,从elf文件中解析出可以注入内核的部分,用bpf_load_program方法完成注入。注入程序bpf_load_program()加入了更复杂的verifier 机制,在运行注入程序之前,先进行一系列的安全检查,最大限度的保证系统的安全。经过安全检查的bpf字节码使用内核JIT进行编译,生成本机汇编指令,附加到内核特定挂钩的程序。最终内核态与用户态通过高效的map机制进行通信,bcc在用户态是使用python进行数据处理的,一图胜千言。

开始编程

了解了bcc工具的工作方式,下面开始写代码,先写python部分,下面是python引入的模块和包,这部分可以在写程序过程中逐步引入,也就是在写python的过程中用到了某个函数就引入相应的模块和包。

#!/usr/bin/env python
# coding=utf-8
from __future__ import print_function
from bcc import BPF
from time import sleep
import argparse
from collections import namedtuple, defaultdict
from threading import Thread, currentThread, Lock

下面是程序选项,可以使用–help来查看可用的选项,效果是这样的:

实现代码如下,具体功能可以看注释:

# 选项参数检错
def range_check(string):value = int(string)if value < 1:msg = "value must be stricly positive, got %d" % (value,)raise argparse.ArgumentTypeError(msg)return value
# 帮助信息的example
examples = """examples:./flow          # trace send/recv flow by host ./flow -p 100   # only trace PID 100
"""
# 使用 python 中的 argparse类 定义选项
parser = argparse.ArgumentParser(description = "Summarize send and recv flow by host",formatter_class = argparse.RawDescriptionHelpFormatter,epilog = examples
)
parser.add_argument("-p", "--pid", help = "Trace this pid only")
parser.add_argument("interval", nargs="?", default=1, type=range_check,help = "output interval, in second (default 1)")
parser.add_argument("count", nargs="?", default=-1, type=range_check,help="number of outputs")
args = parser.parse_args()

接下来是bcc程序中的bpf代码,在python中以这样的形式引入:

bpf_program = """
BPF C 程序
"""

BPF代码

本实例中用到的BPF代码如下,使用了kprobe来探测内核中与网络流量相关的tcp_sendmsg函数和tcp_cleanup_rbuf函数,代码详细作用请看注释:

/*必要的头文件*/
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>
/*定义BPF_HASH中的值*/
struct ipv4_key_t {u32 pid;
};
/*定义两个哈希表,分别以ipv4中发送和接收数据包的进程pid作为关键字*/
BPF_HASH(ipv4_send_bytes, struct ipv4_key_t);
BPF_HASH(ipv4_recv_bytes, struct ipv4_key_t);
/*探测内核中的 tcp_sendmsg 函数 */
int kprobe__tcp_sendmsg(struct pt_regs *ctx, struct sock *sk,struct msghdr *msg, size_t size)
{/*获取当前进程的pid*/u32 pid = bpf_get_current_pid_tgid() >> 32;/*此部分在python里处理,用于替换特定功能的c语句*/FILTER_PID/*获取网络协议的套接字类型*/u16 family = sk->__sk_common.skc_family;/*判断是否是IPv4*/if (family == AF_INET) {/*将当前进程的pid放入ipv4_key结构体中作为ipv4_send_bytes哈希表的关键字*/struct ipv4_key_t ipv4_key = {.pid = pid};/*将size的值作为哈希表的值进行累加*/ipv4_send_bytes.increment(ipv4_key, size);}return 0;
}
/*探测内核中的 tcp_cleanup_rbuf 函数 */
int kprobe__tcp_cleanup_rbuf(struct pt_regs *ctx, struct sock *sk, int copied)
{/*获取当前进程的pid*/u32 pid = bpf_get_current_pid_tgid() >> 32;/*此部分在python里处理,用于替换特定功能的c语句*/FILTER_PID/*获取网络协议的套接字类型*/u16 family = sk->__sk_common.skc_family;u64 *val, zero =0;/*检错*/if (copied <= 0)return 0;/*判断是否是IPv4*/if (family == AF_INET) {/*将当前进程的pid放入ipv4_key结构体中作为ipv4_send_bytes哈希表的关键字*/struct ipv4_key_t ipv4_key = {.pid = pid};/*将copied的值作为哈希表的值进行累加*/ipv4_recv_bytes.increment(ipv4_key, copied);}return 0;
}

几点说明:

  • BPF_HASH 的用法
  • Syntax: BPF_HASH(name [, key_type [, leaf_type [, size]]])
  • Creates a hash map (associative array) named name, with optional parameters.
  • Defaults: BPF_HASH(name, key_type=u64, leaf_type=u64, size=10240)
  • For example:
  • BPF_HASH(start, struct request *);
  • This creates a hash named start where the key is a struct request *, and the value defaults to u64. This hash is used by the disksnoop.py example for saving timestamps for each I/O request, where the key is the pointer to struct request, and the value is the timestamp.
  • Methods (covered later): map.lookup(), map.lookup_or_try_init(), map.delete(), map.update(), map.insert(), map.increment().
  • FILTER_PID
    本示例中的 FILTER_PID 无实际意义,它是在python中使用bpf_program.replace来进行语句替换的,具体作用在下文中python部分会介绍到。

  • ipv4_send_bytes.increment
    这里使用了map.increment()的方法,本实例中是在哈希表ipv4_send_bytes中以ipv4_key为关键字将size作为值进行累加。

  • Syntax: map.increment(key[, increment_amount])
  • Increments the key’s value by increment_amount, which defaults to 1. Used for histograms.

数据处理

刚刚提到FILTER_PID无实际意义,是在python中使用bpf_program.replace来进行语句替换的,现在看下它在python中的处理:

if args.pid:bpf_program = bpf_program.replace('FILTER_PID','if (pid != %s) { return 0; }' % args.pid)
else:bpf_program = bpf_program.replace('FILTER_PID','')

如果使用选项 -p 指定了pid,那么bpf程序中的FILTER_PID会被替换为if (pid != %s) { return 0; },最终在bpf程序中起到过滤指定pid数据的作用。如果没有使用选项 -p 指定 pid,那么就会删除FILTER_PID。也就是说bpf程序中的FILTER_PID不会直接执行,直接执行了会出错,而是经过python处理后才执行。

自定义python函数

# 获取进程名称
def pid_to_comm(pid):try:comm = open("/proc/%s/comm" % pid, "r").read().rstrip()return commexcept IOError:return str(pid)
# 获取pid
SessionKey = namedtuple('Session',['pid'])
def get_ipv4_session_key(k):return SessionKey(pid=k.pid)

初始化bpf

# init bpf
b = BPF(text=bpf_program)
ipv4_send_bytes = b["ipv4_send_bytes"]
ipv4_recv_bytes = b["ipv4_recv_bytes"]

打印标题

# header
print("%-10s %-12s %-10s %-10s %-10s %-10s %-10s" % ("PID", "COMM", "RX_KB", "TX_KB", "RXSUM_KB", "TXSUM_KB", "SUM_KB"))

输出数据

# output
#初始化变量
sumrecv = 0
sumsend = 0
sum_kb = 0
i = 0
exiting = Falsewhile i != args.count and not exiting:try:sleep(args.interval)except KeyboardInterrupt:exiting = Trueipv4_throughput = defaultdict(lambda:[0,0])for k, v in ipv4_send_bytes.items():key=get_ipv4_session_key(k)ipv4_throughput[key][0] = v.valueipv4_send_bytes.clear()for k,v in ipv4_recv_bytes.items():key = get_ipv4_session_key(k)ipv4_throughput[key][1] = v.valueipv4_recv_bytes.clear()if ipv4_throughput:for k, (send_bytes, recv_bytes) in sorted(ipv4_throughput.items(),key=lambda kv: sum(kv[1]),reverse=True):recv_bytes = int(recv_bytes / 1024)send_bytes = int(send_bytes / 1024)sumrecv += recv_bytessumsend += send_bytessum_kb = sumrecv + sumsendprint("%-10d %-12.12s %-10d %-10d %-10d %-10d %-10d" % (k.pid, pid_to_comm(k.pid), recv_bytes, send_bytes, sumrecv, sumsend, sum_kb))i += 1

这部分是python处理数据的过程,需要注意的是:
ipv4_throughput = defaultdict(lambda:[0,0])这里创建了一个名为ipv4_throughput的字典,将名为ipv4_send_bytesipv4_recv_bytes两个哈希表中的数据分别放到了名为ipv4_throughput的字典中,这样使得后续的数据处理更加统一。

for k, v in ipv4_send_bytes.items():这里将哈希表ipv4_send_bytes中的关键字和值使用.items的方法分别存放在了k和v中。

key=get_ipv4_session_key(k)这里调用了get_ipv4_session_key(k)函数获取到了关键字,也就是pid。

到此,一个基本的MVP就写好了,可以先跑一下,运行结果如下:

可以看到,内核中的流量数据已经提取出来了。当然,本文只是分享如何编写一个bcc程序,目前这个程序还有很多升级的空间,例如:

  • 本实例只统计IPv4的流量,还可以加入统计IPv6的流量
  • 可以添加更多的字段,如源地址,源端口,目标地址,目标端口
  • 可以加入更多的选项参数等

目前介绍到这里,我还会继续优化程序的,感谢阅读。

使用eBPFBCC提取内核网络流量信息相关推荐

  1. 使用eBPFbcc提取内核网络流量信息(二)

    通过上次从0开始编写自己的bcc程序的介绍,我们已经用编写的bcc程序提取出内核网络中数据关键字段为进程的PID,进程的名字,进程的收包实时流量.发包实时流量,收包流量总和,发包流量总和,总的收发流量 ...

  2. Linux 内核 | 网络流量限速方案大 PK

    网络流量限速是一个经久不衰的话题,Linux 内核中已经实现了若干种流量限速的方式. 最简单的方式是通过定期采集速率,在超过指定的速率后直接丢包,但这种方案效果不佳,不能精准地将流量控制在指定的速率. ...

  3. C# IPGlobalStatistics获取本机网络流量信息

    例子如图: 完整代码: 引入命名空间: using System.Net.NetworkInformation; 完整代码: namespace IPGlobalStatics { public pa ...

  4. 网络分流器-网络分流器TAP网络流量监控

    戎腾网络分流器作为网络安全重要装备,是整个网络安全领域网络监控前端最关键的装备! 今天我们详解网络流量监控! 网络分流器TAP ATCA网络分流器支持多用户高密度 网络分流器DPI检测五元组过滤 网络 ...

  5. linux网络流量查看命令

    网卡流量 1.iftop命令 iftop可以用来监控网卡的实时流量(可以指定网段).反向解析IP.显示端口信息.TCP/IP连接等 官网:http://www.ex-parrot.com/~pdw/i ...

  6. linux 查看网络流量来源_linux中查看网卡流量六种方法

    方法一.nload工具 源码包路径: 查看参数帮助命令: nload –help -a:这个好像是全部数据的刷新时间周期,单位是秒,默认是300. -i:进入网卡的流量图的显示比例最大值设置,默认10 ...

  7. 局域网网络流量监控_Linux网络安全运维:网络流量监控与分析工具Ntop和Ntopng

    一次性付费进群,长期免费索取教程,没有付费教程. 进微信群回复公众号:微信群:QQ群:460500587  教程列表 见微信公众号底部菜单 |  本文底部有推荐书籍  微信公众号:计算机与网络安全 I ...

  8. Linux中一行命令查看网卡流量、统计网络流量的各种实现方法

    Linux中一行命令查看网卡流量.统计网络流量的各种实现方法. 方法一.nload工具 源码包路径: wget http://heanet.dl.sourceforge.net/project/nlo ...

  9. 局域网网络流量监控_【干货】Linux网络安全运维:网络流量监控与分析工具Ntop和Ntopng...

    本文授权转载自微信公众号:计算机与网络安全,转载请联系授权.对于单台服务器网络故障的排查,iftop工具可以轻松实现,但是在监控一个庞大的服务器网络,并且要分析每台主机以及端口的网络状态时,iftop ...

最新文章

  1. buHdoZer‘s Arch
  2. 快速径向对称 只检测暗对称 代码(2)
  3. 把Spring Boot项目打为可执行jar包
  4. 访百度奥运logo设计师李兴钢:虚实之间最美的呈现
  5. C# 引用类型与值类型转换-装箱和拆箱
  6. .net aspose.words 域加载图片_使用Python批量替换csdn文章的图片链接
  7. 利用Object.defineProperty实现Vue数据双向绑定
  8. 运行Android Studio自带模拟器报:Guest isn't online after 7 second...
  9. 谷粒商城:SPU管理规格显示404
  10. php arcsin,三角函数在线计算器
  11. 中国地级以上城市的经纬度——excel文件
  12. 信息系统项目管理师---综合类计算
  13. 2021最新前端面试题
  14. ICH1/ICH2/ICH3/ICH4/ICH5/ICH6/ICH7/ICH8/ICH9的区别和联系
  15. 电脑如何备份文件,怎么同步?
  16. 网络基础——网络层(ip协议详解)
  17. modelsim之inout类型tb文件编写及仿真
  18. 老胡的周刊(第084期)
  19. javascript中的getElementById、getElementsByName、getElementByTagName详解
  20. Shortcuts使用解析(一)

热门文章

  1. 试用 smartdraw 2010 方便快捷的图表工具
  2. Django 2.2 LTS 发布,长期支持版来了
  3. 越来越多动物正在灭绝,“AI+动物”能否改变这一局面?[图]
  4. Micropython TPYBoard 智能温控小风扇资料分享
  5. Hibernate中两种获取Session的方式
  6. 企业数据中心和互联网数据中心有何不同?
  7. 转-OpenJDK源码阅读导航跟编译
  8. PHP中把stdClass Object转array的几个方法
  9. golang 执行命令 设置超时
  10. python3 字符串 hex 相互转换 代替python2 decode(‘hex’)