Bpf截取Linux网络流量信息
一、环境搭建
二、基于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);
以下为参考链接,为陈老师内核之旅网站文章,大家可以关注下,干货很多。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。