利用netfilter、libpcap、scapy抓取数据包,分析netfilter、libpcap、scapy、tcpdump、wireshark的cpu及内存占用率,使用netfilter、scapy、tcpdump、wireshark实现openstack网络架构上patch-tun端口抓包,查阅了一些其他的数据包捕获技术(例如,libpcap-mmap、pf_ring、dsniff以及Windows的HOOK技术)。
一.netfilter
1.Netfilter/IPTables是Linux2.4.x之后新一代的Linux防火墙机制,是linux内核的一个子系统,在整个网络流程的若干位置放置了一些检测点(HOOK),在每个检测点上登记了一些处理函数进行处理,可以实现诸如数据包过滤、网络地址转换(NAT)和基于协议类型的连接跟踪。
2. 在IP层有5个HOOK点:
[1]:NF_IP_PRE_ROUTING:刚刚进入网络层的数据包(刚刚进行完版本号,校验和等检测), 目的地址转换在此点进行;
[2]:NF_IP_LOCAL_IN:经路由查找后,送往本机的数据包通过此检查点; [3]:NF_IP_FORWARD:要转发的包通过此检测点;
[4]:NF_IP_POST_ROUTING:所有马上便要通过网络设备出去的包通过此检测点;
[5]:NF_IP_LOCAL_OUT:本机进程发出的包通过此检测点。
3.使用nf_register_hook函数可以加入自己的代码,函数原型:int nf_register_hook(struct nf_hook_ops *reg),需要生成一个nf_hook_ops结构的实例:
struct nf_hook_ops {
struct list_head list;
/* User fills in from here down. */
nf_hookfn *hook;//用户定义的钩子函数
struct module *owner;//注册这个钩子函数的模块,
u_int8_t pf;//索引到特定协议,ip层是PF_INET
unsigned int hooknum;//索引到特定编号,即选择的HOOK点
/* Hooks are ordered in ascending priority. */
int priority;//每个HOOK点可以有多个处理函数,定义处理函数的优先级
};
4.netfilter2.c:
#include <linux/netfilter.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/inet.h>
unsigned int my_hookfn(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in, //net_device网络设备结构体,用于内核自身(设备驱动、上层协议等)对网络设备的操作
const struct net_device *out,
//net_device结构体:网卡硬件类信息,统计类信息,上层协议处理接口,流控接口
int (*okfn)(struct sk_buff *))
{
struct iphdr *iph; //iphdr:linux下ip数据包的描述结构体
iph = ip_hdr(skb);
printk(KERN_INFO"%pI4->%pI4\n",&iph->saddr,&iph->daddr); //在内核中运行的printf,%p用来输出指针类型自身的值
return NF_ACCEPT; //返回值,继续正常传输数据报
}
/* A netfilter instance to use */
static struct nf_hook_ops nfho1 = {
.hook = my_hookfn,
.pf = PF_INET,
.hooknum = NF_INET_PRE_ROUTING, //刚进入网络层
.priority = NF_IP_PRI_FIRST,
.owner = THIS_MODULE,
};
static int __init catch_init(void)
{
if (nf_register_hook(&nfho1)) {
printk(KERN_ERR"nf_register_hook() failed\n");
return -1;
}
return 0;
}
static void __exit catch_exit(void)
{
nf_unregister_hook(&nfho1);
}
module_init(catch_init);
module_exit(catch_exit);
MODULE_LICENSE("GPL");
5.Makefile:
ifneq ($(KERNELRELEASE),)
obj-m += netfilter2.o
else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.ko *.symvers *.order *.makers
endif
6.使用tail -f /var/log/messages查看捕获到的数据包
二.libpcap
1.libpcap是一个网络数据包捕获函数库,对网卡接收到的链路层数据包进行过滤,根据过滤规则决定是否接收或拷贝,若没有规则,则全部上交。libpcap使用流程:确定嗅探的端口,使用文件句柄传入需要嗅探的设备,创建规则集合,进入pcap主循环。
2. libpcap的抓包框架:
pcap_lookupdev():查找网络设备,返回可被pcap_open_live()函数调用的网络设备名指针。
pcap_lookupnet():获得指定网络设备的网络号和掩码。
pcap_open_live():打开设备。
pcap_compile():函数用于将用户制定的过滤策略编译到过滤程序中
pcap_setfilter():函数用于设置过滤器
pcap_loop():与pcap_next()和pcap_next_ex()两个函数一样用来捕获数据包
pcap_close():函数用于关闭网络设备,释放资源
3.利用libpcap抓包的工作流程:
4.libpcaptest3.c:
#include <stdio.h>
#include <pcap.h>
#include <arpa/inet.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 1514
struct ether_header
{
unsigned char mac_dhost[6]; //以太网目的地址
unsigned char mac_shost[6]; //以太网源地址
unsigned short mac_type; //以太网数据包类型
};
struct ether_header_IP //IP数据包
{
unsigned char mac_dhost[6];
unsigned char mac_shost[6];
unsigned short mac_type;
unsigned char ip_head[12]; //版本号-首部校验和的12字节
unsigned char ip_shost[4]; //源IP
unsigned char ip_dhost[4]; //目的IP
};
struct ether_header_ARP //ARP数据包
{
unsigned char mac_dhost[6];
unsigned char mac_shost[6];
unsigned short mac_type;
unsigned char arp_head[8]; //arp首部
unsigned char arp_sha[6]; //源MAC
unsigned char arp_spa[4]; //源IP
unsigned char arp_tha[6]; //目的MAC
unsigned char arp_tpa[4]; //目的IP
};
/*******************************回调函数************************************/
void ethernet_protocol_callback(unsigned char *argument,const struct pcap_pkthdr *packet_heaher,const unsigned char *packet_content)
{
unsigned char *mac_string;
unsigned short mac_t;
unsigned char *ip_s,*ip_d;
struct ether_header *ethernet_protocol;
ethernet_protocol = (struct ether_header *)packet_content;
mac_t=ntohs(ethernet_protocol->mac_type);//数据包类型
if (mac_t==0x0800){ //IP数据包
struct ether_header_IP *ethernet_protocol_IP;
ethernet_protocol_IP = (struct ether_header_IP *)packet_content;
printf("----------------------------------------------------\n");
printf("%s", ctime((time_t *)&(packet_heaher->ts.tv_sec))); //转换时间
ip_s=(unsigned char *)ethernet_protocol_IP->ip_shost;
ip_d=(unsigned char *)ethernet_protocol_IP->ip_dhost;
printf("ip_s:%3d.%3d.%3d.%3d\n",*(ip_s+0),*(ip_s+1),*(ip_s+2),*(ip_s+3));
printf("ip_d:%3d.%3d.%3d.%3d\n",*(ip_d+0),*(ip_d+1),*(ip_d+2),*(ip_d+3));
}
else if(mac_t==0x0806){ //ARP数据包
struct ether_header_ARP *ethernet_protocol_ARP;
ethernet_protocol_ARP = (struct ether_header_ARP *)packet_content;
printf("----------------------------------------------------\n");
printf("%s", ctime((time_t *)&(packet_heaher->ts.tv_sec))); //转换时间
ip_s=(unsigned char *)ethernet_protocol_ARP->arp_spa;
ip_d=(unsigned char *)ethernet_protocol_ARP->arp_tpa;
printf("ip_s:%3d.%3d.%3d.%3d\n",*(ip_s+0),*(ip_s+1),*(ip_s+2),*(ip_s+3));
printf("ip_d:%3d.%3d.%3d.%3d\n",*(ip_d+0),*(ip_d+1),*(ip_d+2),*(ip_d+3));
}
switch(mac_t)
{
case 0x0800:printf("The network layer is IP protocol\n");break;//ip
case 0x0806:printf("The network layer is ARP protocol\n");break;//arp
case 0x0835:printf("The network layer is RARP protocol\n");break;//rarp
default:break;
}
usleep(800*1000); //挂起0.8s
}
int main(int argc, char *argv[])
{
char error_content[100]; //出错信息
pcap_t * pcap_handle;
unsigned char *mac_string;
unsigned short ethernet_type; //以太网类型
char *net_interface = "eth0"; //接口名字
struct pcap_pkthdr protocol_header;
struct ether_header *ethernet_protocol;
pcap_handle = pcap_open_live(net_interface,BUFSIZE,1,0,error_content);//打开网络接口
if(pcap_loop(pcap_handle,-1,ethernet_protocol_callback,NULL) < 0)
//持续抓包,使用-1
{
perror("pcap_loop"); //用来将上一个函数发生错误的原因输出到stderr
}
pcap_close(pcap_handle); //关闭接口
return 0;
}
5. gcc -o libpcaptest3 libpcaptest3.c -lpcap
./libpcaptest3
三.scapy
1.scapy是一个可用于网络嗅探的非常强大的第三方库,内部实现了大量的网络协议,(DNS,ARP,IP,TCP,UDP等等),能够伪造或者解码大量的网络协议数据包,能够发送、捕捉、匹配请求和回复包。
2.sniff(filter="",iface="any", prn=function, count=N)
filter可以过滤:源/目的地址,协议,端口号等(例如,filter=”ip src 10.0.0.210 and icmp”);
iface用来指定要在哪个网络接口上进行抓包(通常不指定即所有网络接口);
prn指定回调函数,每当一个符合filter的报文被探测到时,就会执行回调函数,通常使用lambda表达式来写回调函数(例如,prn=lambda x:x.sprintf("%IP.src%:%TCP.sport% -> %IP.dst%:%TCP.dport%”)打印对话的IP地址及其端口号)
count指定最多嗅探多少个报文(是指符合filter条件的报文,而非所有报文)。
3. sp.py
from scapy.all import *
import scapy
pc=sniff(iface='eth0',prn=lambda x:x.summary())
//过滤出经过eth0端口的所有数据报,打印出报文摘要
四.占用资源对比
|
tcpdump |
wireshark |
scapy |
netfilter |
libpcap |
cpu占用(%) |
0.0 |
0.2 |
1.6 |
0.0 |
0.0 |
内存占用(%) |
0.3 |
3.8 |
2.1 |
0.0 |
0.1 |
可以看到除了wireshark和scapy占用内存与cpu较大,其余方法的内存与cpu占用均较小,netfilter直接将捕获结果写入/var/log/messages中,tcpdump可以使用-w命令写入文件,但是该文件只能使用tcpdump -r查看。
五.openstack网络架构上patch-tun端口抓包记录
- VXLAN模式的基本结构:
假设虚拟机VM1发送数据包给VM2,则数据包流向如图所示:虚拟机网卡连接物理机的tap设备(A)——》通过VETH pair连接到网桥qbr的端口vnet(B)——》通过VETH pair连接到网桥br-int的端口qvo(D)——》patch-tun(E)——》patch-int(F)——》br-tun根据转发规则转发。因此虚拟机之间的通信一定会经过内部端口patch-tun。
·为何TAP不直接连接到br-int上?
OpenStack需要通过iptables实现security group的安全策略功能,而目前openvswitch不支持应用iptables规则的Tap设备,所以TAP设备A没有直接连接到网桥br-int上。qbr的存在主要是为了辅助iptables来实现security group功能,有时候也被称为安全网桥。
2.在物理机10.190.88.29的patch-tun端口上抓取数据包:
由于patch-tun是内部端口,所以需要在patch-tun端口上添加镜像,设置所有经过patch-tun端口的数据包同样发送到ptun1,从而捕获到虚拟机之间通信的数据包:
利用tcpdump监听ptun1上的数据包:tcpdump -i ptun1
3.tcpdump、wireshark、scapy在ptun端口抓包的功耗分析:
(1)tcpdump抓包:
使用ps aux|grep tcpdump命令,可以看到tcpdump进程几乎不占用CPU及内存:
(2)wireshark抓包:
而使用tshark命令抓包,若将捕获到的数据包写入文件,占用CPU为0.1%,不写入文件直接输出占用CPU为0.3%。
4.scapy模块抓包:
利用python的scapy模块,捕获ptun端口上的数据包,占用CPU为3.4%,明显高于(1)和(2):
5.netfilter捕获所有进入网络层的数据包,可以捕获到虚拟机相互通信的数据包;将libpcaptest.c文件中的接口名net_interface修改为”ptun1”,但是无法捕获到虚拟机之间的数据包,尝试将patch-tun收到/发送的数据包镜像到em1?
ovs-vsctl add-port br-int em1
ovs-vsctl -- set Bridge br-int [email protected] -- [email protected] get Port em1 -- [email protected] get Port patch-tun -- [email protected] create Mirror name=mymirror2 [email protected] [email protected] [email protected] select_all=1
六.其他数据包捕获技术
1.libpcap-mmap
利用libpcap过滤网络数据包的工作流程:
libpcap接收数据包时会产生中断,若将数据包拷贝到内核、用户空间,将消耗大量的cpu资源。libpcap-mmap减少了内核的拷贝次数和系统的调用开销。
libpcap-mmap建立核心态内存和用户态内存之间的映射,可以通过调用系统函数recvfrom()将数据包从网卡上直接传送到用户态内存中,从而减少数据拷贝的次数。
2.pf_ring
在linux内核层添加带环状缓存的socket,提供socket接口,将网卡接收到的数据包拷贝到该环状缓存中,释放原数据包,应用层可以通过socket连接从环状缓存中读取数据包。
3.dsniff
dsniff是一个综合性的网络嗅探工具包,可以进行网络监视、针对SSH和SSL发起攻击、发起主动欺骗、分析数据包获取密码等。
4.利用windows的HOOK技术
将编写的具有抓包功能的动态链接库(DLL)程序模块注入每个进程中,选择需要抓包的目标进程,在DLL中拦截该进程对操作系统套接字函数接口(SOCKET API)的调用,利用动态装载技术,获取套接字函数接口的地址,保存该地址,修改该地址为自定义的抓包函数,抓包完成后恢复原套接字函数地址。
抓包流程图: