Bpf截取Linux网络流量信息

Bpf截取Linux网络流量信息

一、环境搭建

参考:基于Linux bpf跟踪文件读写_Configure-Handle的博客-CSDN博客

二、基于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
from socket import inet_ntop, AF_INET
from struct import pack
from time import sleep, strftime
from subprocess import call


# 选项参数检错
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()

bpf_program = """
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>

/*定义BPF_HASH中的值*/
struct ipv4_key_t {
    u32 pid;
    u32 saddr;
    u32 daddr;
    u16 lport;
    u16 dport;
};

/*定义两个哈希表,分别以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 dport = 0;
    /*获取网络协议的套接字类型*/
    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};
        ipv4_key.saddr = sk->__sk_common.skc_rcv_saddr;
        ipv4_key.daddr = sk->__sk_common.skc_daddr;
        ipv4_key.lport = sk->__sk_common.skc_num;
        dport = sk->__sk_common.skc_dport;
        ipv4_key.dport = ntohs(dport);

        /*将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 dport = 0;
    /*获取网络协议的套接字类型*/
    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};

        ipv4_key.saddr = sk->__sk_common.skc_rcv_saddr;
        ipv4_key.daddr = sk->__sk_common.skc_daddr;
        ipv4_key.lport = sk->__sk_common.skc_num;
        dport = sk->__sk_common.skc_dport;
        ipv4_key.dport = ntohs(dport);

        /*将copied的值作为哈希表的值进行累加*/
        ipv4_recv_bytes.increment(ipv4_key, copied);
    }
    return 0;
}
"""

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','')

# 获取进程名称
def pid_to_comm(pid):
    try:
        comm = open("/proc/%s/comm" % pid, "r").read().rstrip()
        return comm
    except IOError:
        return str(pid)

# 获取pid
SessionKey = namedtuple('Session',['pid', 'laddr', 'lport', 'daddr', 'dport'])

def get_ipv4_session_key(k):
    return SessionKey(pid=k.pid, laddr=inet_ntop(AF_INET, pack("I", k.saddr)),
            lport=k.lport, daddr=inet_ntop(AF_INET, pack("I", k.daddr)), dport=k.dport)

# 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 %-21s %-21s" % ("PID", "COMM", "RX_KB", "TX_KB", "RXSUM_KB", "TXSUM_KB", "SUM_KB", "LADDR:LPORT", "DADDR:DPORT"))

# output
#初始化变量
sumrecv = 0
sumsend = 0
sum_kb = 0
i = 0
exiting = False

while i != args.count and not exiting:
        try:
                sleep(args.interval)
        except KeyboardInterrupt:
                exiting = True

        ipv4_throughput = defaultdict(lambda:[0,0])
        for k, v in ipv4_send_bytes.items():
                key=get_ipv4_session_key(k)
                ipv4_throughput[key][0] = v.value
        ipv4_send_bytes.clear()

        for k,v in ipv4_recv_bytes.items():
                key = get_ipv4_session_key(k)
                ipv4_throughput[key][1] = v.value
        ipv4_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_bytes
                        sumsend += send_bytes
                        sum_kb = sumrecv + sumsend
                        print("%-10d %-12.12s %-10d %-10d %-10d %-10d %-10d %-21s %-21s" % (k.pid, pid_to_comm(k.pid), recv_bytes, send_bytes, sumrecv, sumsend, sum_kb, k.laddr + ":" + str(k.lport), k.daddr + ":" + str(k.dport),))
        i += 1

效果展示:

root@curtis-Aspire-E5-471G:/home/curtis/code/bpf/eBPF-master/write_code# ./flow.py
PID        COMM         RX_KB      TX_KB      RXSUM_KB   TXSUM_KB   SUM_KB     LADDR:LPORT           DADDR:DPORT
13329      GeckoMain    57         1          57         1          58         192.168.0.103:59436   14.215.177.38:443
13329      GeckoMain    4          2          61         3          64         192.168.0.103:53346   14.215.177.39:443
13329      GeckoMain    360        3          421        6          427        192.168.0.103:43664   113.113.73.35:443
13329      GeckoMain    46         2          467        8          475        192.168.0.103:59436   14.215.177.38:443
13329      GeckoMain    25         1          492        9          501        192.168.0.103:38792   111.123.247.35:443
13329      GeckoMain    24         1          516        10         526        192.168.0.103:38898   113.113.73.36:443
13329      GeckoMain    17         1          533        11         544        192.168.0.103:38896   113.113.73.36:443
13329      GeckoMain    5          1          538        12         550        192.168.0.103:58922   14.152.86.38:443

三、在kprobe中如何获取将skc_daddr&skc_daddr转换成IP

kernel 5.0 结构体sock_common说明

/**
 *	struct sock_common - minimal network layer representation of sockets  //套接字的最小网络层表示
 *	@skc_daddr: Foreign IPv4 addr  //外部ivp4地址
 *	@skc_rcv_saddr: Bound local IPv4 addr //绑定的本地 IPv4 地址
 *	@skc_addrpair: 8-byte-aligned __u64 union of @skc_daddr & @skc_rcv_saddr
 *	@skc_hash: hash value used with various protocol lookup tables
 *	@skc_u16hashes: two u16 hash values used by UDP lookup tables
 *	@skc_dport: placeholder for inet_dport/tw_dport
 *	@skc_num: placeholder for inet_num/tw_num
 *	@skc_portpair: __u32 union of @skc_dport & @skc_num
 *	@skc_family: network address family //网络地址类型 ipv4/ipv6
 *	@skc_state: Connection state
 *	@skc_reuse: %SO_REUSEADDR setting
 *	@skc_reuseport: %SO_REUSEPORT setting
 *	@skc_ipv6only: socket is IPV6 only
 *	@skc_net_refcnt: socket is using net ref counting
 *	@skc_bound_dev_if: bound device index if != 0
 *	@skc_bind_node: bind hash linkage for various protocol lookup tables
 *	@skc_portaddr_node: second hash linkage for UDP/UDP-Lite protocol
 *	@skc_prot: protocol handlers inside a network family
 *	@skc_net: reference to the network namespace of this socket
 *	@skc_v6_daddr: IPV6 destination address
 *	@skc_v6_rcv_saddr: IPV6 source address
 *	@skc_cookie: socket's cookie value
 *	@skc_node: main hash linkage for various protocol lookup tables
 *	@skc_nulls_node: main hash linkage for TCP/UDP/UDP-Lite protocol
 *	@skc_tx_queue_mapping: tx queue number for this connection
 *	@skc_rx_queue_mapping: rx queue number for this connection
 *	@skc_flags: place holder for sk_flags
 *		%SO_LINGER (l_onoff), %SO_BROADCAST, %SO_KEEPALIVE,
 *		%SO_OOBINLINE settings, %SO_TIMESTAMPING settings
 *	@skc_listener: connection request listener socket (aka rsk_listener)
 *		[union with @skc_flags]
 *	@skc_tw_dr: (aka tw_dr) ptr to &struct inet_timewait_death_row
 *		[union with @skc_flags]
 *	@skc_incoming_cpu: record/match cpu processing incoming packets
 *	@skc_rcv_wnd: (aka rsk_rcv_wnd) TCP receive window size (possibly scaled)
 *		[union with @skc_incoming_cpu]
 *	@skc_tw_rcv_nxt: (aka tw_rcv_nxt) TCP window next expected seq number
 *		[union with @skc_incoming_cpu]
 *	@skc_refcnt: reference count
 *
 *	This is the minimal network layer representation of sockets, the header
 *	for struct sock and struct inet_timewait_sock.
 */
struct sock_common {
	union {
		__addrpair	skc_addrpair;
		struct {
			__be32	skc_daddr;
			__be32	skc_rcv_saddr;
		};
	};
	union  {
		unsigned int	skc_hash;
		__u16		skc_u16hashes[2];
	};
	/* skc_dport && skc_num must be grouped as well */
	union {
		__portpair	skc_portpair;
		struct {
			__be16	skc_dport;	//typedef __u32 __bitwise __be32;
			__u16	skc_num;
		};
	};

	unsigned short		skc_family;
	volatile unsigned char	skc_state;
	unsigned char		skc_reuse:4;
	unsigned char		skc_reuseport:1;
	unsigned char		skc_ipv6only:1;
	unsigned char		skc_net_refcnt:1;
	int			skc_bound_dev_if;
	union {
		struct hlist_node	skc_bind_node;
		struct hlist_node	skc_portaddr_node;
	};
	struct proto		*skc_prot;
	possible_net_t		skc_net;

#if IS_ENABLED(CONFIG_IPV6)
	struct in6_addr		skc_v6_daddr;
	struct in6_addr		skc_v6_rcv_saddr;
#endif

	atomic64_t		skc_cookie;

	/* following fields are padding to force
	 * offset(struct sock, sk_refcnt) == 128 on 64bit arches
	 * assuming IPV6 is enabled. We use this padding differently
	 * for different kind of 'sockets'
	 */
	union {
		unsigned long	skc_flags;
		struct sock	*skc_listener; /* request_sock */
		struct inet_timewait_death_row *skc_tw_dr; /* inet_timewait_sock */
	};
	/*
	 * fields between dontcopy_begin/dontcopy_end
	 * are not copied in sock_copy()
	 */
	/* private: */
	int			skc_dontcopy_begin[0];
	/* public: */
	union {
		struct hlist_node	skc_node;
		struct hlist_nulls_node skc_nulls_node;
	};
	unsigned short		skc_tx_queue_mapping;
#ifdef CONFIG_SOCK_RX_QUEUE_MAPPING
	unsigned short		skc_rx_queue_mapping;
#endif
	union {
		int		skc_incoming_cpu;
		u32		skc_rcv_wnd;
		u32		skc_tw_rcv_nxt; /* struct tcp_timewait_sock  */
	};

	refcount_t		skc_refcnt;
	/* private: */
	int                     skc_dontcopy_end[0];
	union {
		u32		skc_rxhash;
		u32		skc_window_clamp;
		u32		skc_tw_snd_nxt; /* struct tcp_timewait_sock */
	};
	/* public: */
};

从结构体struct sock_common中获取到的skc_daddr和skc_daddr为无符号整型,如何转化成我们常见的IP表现形式(如192.168.1.1)。

//You should use the %pI4 extended format specifiers provided by printk():
printk(KERN_DEBUG "IP addres = %pI4\n", &local_ip);

//如果想要把ip这个字符串的值赋值给变量
#define MAX_ip_LEN 15
char ip_str[MAX_IP_LEN];
sprintf(ip_str, "%pI4", skc_daddr);

以下为参考链接,为陈老师内核之旅网站文章,大家可以关注下,干货很多。

参考链接:使用bcc提取内核网络流量信息 (kerneltravel.net)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340