在网络分析和安全领域,数据包嗅探和过滤是两项基础而关键的技术。Berkeley Packet Filter(BPF)是一种广泛使用的过滤技术,它允许用户定义一组规则来决定哪些数据包应该被捕获和分析。
BPF和eBPF简介
BPF(Berkeley Packet Filter)和eBPF(extended Berkeley Packet Filter)是Linux内核中强大的网络数据包过滤和处理工具。
BPF起源于1992年,由Steven McCanne和Van Jacobson在UNIX平台上提出,它最初用于网络数据包过滤,提供了一种用户级别的数据包捕获架构。BPF通过引入虚拟机设计和优化数据复制策略,实现了高效的数据包过滤性能。经典的BPF被广泛应用在如tcpdump等网络监控工具中,其可以通过特定的汇编指令来指定过滤规则[。
到了2014年,Alexei Starovoitov对BPF进行了扩展,形成了eBPF。eBPF不仅适用于网络数据包过滤,还可以用于性能分析、系统追踪、网络优化等多种场景。eBPF具有更多的寄存器和更大的存储空间,支持更复杂的程序编写,并且性能比传统BPF提高了4倍。
eBPF的运行需要通过严格的验证器检查,保证其安全性,然后通过JIT(Just-In-Time)编译方式,将字节码转换为本地指令执行。eBPF程序可以实时加载到内核中运行,而无需重启内核,这为动态追踪和系统监控提供了极大的便利。
综上所述,BPF和eBPF的发展极大增强了Linux的网络监控和系统管理能力,使得内核级别的任务能够以更加灵活和安全的方式执行。这些技术不仅被网络管理员广泛使用,同时也得到了许多大型互联网公司如Facebook、Netflix的青睞,它们在这些公司的技术栈中发挥着重要作用。
BPF架构
BPF(伯克利包过滤器)和eBPF(扩展伯克利包过滤器)的架构是现代Linux网络和系统监控的重要组成部分。
BPF最初设计用于网络数据包过滤,但随着时间的推移和技术的发展,eBPF作为其扩展版本,不仅增强了原有的功能,还拓宽了应用范围,使其成为内核级别的通用执行引擎。下面将深入探讨BPF和eBPF的架构和组成部分,以及它们在现代技术中的重要性:
- BPF架构的基础
- 原始架构:BPF最初被设计为一个在用户空间和内核空间之间提供接口的简单虚拟机。该设计允许特定的网络数据包根据用户定义的规则被捕获和过滤。BPF的原始实现侧重于数据包的捕获和过滤,通过一个小型的指令集实现,这有助于简化处理逻辑并提高处理效率。
- 技术革新:BPF带来了两项主要的技术革新:一是高效的虚拟机设计;二是数据复制策略的优化,只复制与过滤有关的数据包部分,这大大减少了数据处理的负载。
- eBPF的演进
- 架构扩展:eBPF对BPF的架构进行了扩展,增加了更多的寄存器、更宽的寄存器宽度、以及更大的存储空间。这些改进使得eBPF可以支持更复杂的程序编写和更高性能的数据处理。eBPF也引入了更安全的措施,如验证器,确保加载到内核的程序是安全且不会引发系统崩溃。
- 提升性能:eBPF的性能比传统的BPF快4倍,这得益于其优化的指令集和JIT编译方式。这种性能的提升使得eBPF能够更广泛地应用于各种场景,包括网络监控、性能分析、系统追踪等。
- eBPF的核心组件
- 字节码和JIT编译器:eBPF程序首先被编写成高级语言(如C),然后通过编译器(如LLVM)转换为eBPF字节码。这些字节码随后可以通过JIT编译器转换成机器码,以实现高效执行。
- 验证器和安全性:为了保护系统免受恶意代码的影响,eBPF包含了一个验证器,用于确保所有字节码在执行前都是安全的。这防止了无限循环、非法指令或越界内存访问的可能性。
- 用户空间加载器和交互:用户空间工具(如bpftool和bpftrace)允许用户加载、管理和监控eBPF程序的状态。这些工具还可以创建和管理eBPF地图,这是内核和用户空间之间通信的桥梁。
- 钩子和辅助函数:eBPF可以通过钩子接入内核中的特定点,如系统调用、跟踪点或网络事件。辅助函数则为eBPF程序提供了一组预定义的内核函数,使得eBPF程序能够执行更复杂的操作。
BPF过滤语法
BPF (Berkeley Packet Filter)过滤语法是一种强大的网络数据包过滤语言,主要用于网络监控、数据包分析以及安全审计等领域。它可以通过特定的表达式精确地筛选出符合特定条件的数据包。由于不同的网络工具和应用程序可能对BPF语法有略微不同的支持和扩展,因此在具体使用时可能需要参考相应的文档和工具的说明。
BPF过滤语法的基础包括多个要素,如类型(Type)、方向(Direction)、协议(Protocol)和逻辑运算符。以下将详细介绍这些要素以及如何组合它们来创建有效的过滤规则:
- 类型(Type) :
- Type定义了要过滤的数据包的类型,例如主机(host)、网络(net)和端口(port)。例如,
host 192.168.1.1
表示过滤源或目标IP地址为192.168.1.1的数据包。 net 192.168.1.0/24
表示过滤源或目标网段为192.168.1.0/24的数据包。
- Type定义了要过滤的数据包的类型,例如主机(host)、网络(net)和端口(port)。例如,
- 方向(Direction) :
- Direction指定数据包的传输方向,如源地址(src)、目标地址(dst)。例如,
src host 192.168.1.1
表示过滤源IP地址为192.168.1.1的数据包。 dst port 80
表示过滤目标端口为80的数据包。
- Direction指定数据包的传输方向,如源地址(src)、目标地址(dst)。例如,
- 协议(Protocol) :
- Protocol指定要过滤的数据包所属的协议,如IP、TCP、UDP、HTTP等。例如,
ip
表示过滤IP协议的数据包,tcp
表示过滤TCP协议的数据包。
- Protocol指定要过滤的数据包所属的协议,如IP、TCP、UDP、HTTP等。例如,
- 逻辑运算符 :
- 逻辑运算符用于组合多个过滤条件,如与(&&)、或(||)、非(!)。例如,
host 192.168.1.1 && port 80
表示过滤源或目标IP地址为192.168.1.1且端口为80的数据包。 tcp || udp
表示过滤TCP或UDP协议的数据包。
- 逻辑运算符用于组合多个过滤条件,如与(&&)、或(||)、非(!)。例如,
- 综合应用示例 :
- 一个更复杂的示例是
host 192.168.1.1 && port 8080
,这条规则表示只过滤那些源地址或目的地址为192.168.1.1且端口为8080的数据包。 - 另一个例子是
not arp and not icmp
,表示过滤掉除了ARP和ICMP之外的所有数据包。
- 一个更复杂的示例是
BPF在网络安全中的应用
BPF(Berkeley Packet Filter)在网络安全中的应用广泛且具有重要意义。以下将详细探讨 BPF 在网络安全领域的应用:
- 流量监控与分析
- 数据捕获和过滤:BPF最初设计用于数据包过滤,它能够高效地捕获和分析网络数据包。通过BPF,管理员可以监控实时流量并过滤出异常或恶意的数据包,从而及早发现潜在的威胁。
- 性能分析:BPF技术能够提供高精度、低开销的流量监控,帮助管理员了解网络流量模式,优化网络性能,确保网络的稳定运行。
- 入侵检测与防御
- 实时检测:利用BPF的高效数据包处理能力,可以实时地监控网络流量,检测DDoS攻击、端口扫描等恶意行为[。通过即时监测和响应,系统能够快速拦截和防御这些攻击。
- 安全事件记录:BPF可以在不增加系统负担的情况下记录安全事件,为事后分析提供详尽的日志数据。记录下的信息有助于追踪攻击来源并制定防御策略。
- 网络隔离与防火墙
- 防火墙规则:BPF可以用来实现复杂的防火墙规则,精确控制进出网络的数据包。它可以根据源地址、目的地址、端口号等条件进行过滤,增强网络边界的安全保护。
- 虚拟私有网络:通过BPF技术,可以实现VPN功能,对数据传输进行加密,确保数据在传输过程中的安全性。这对于远程访问和公网上的私密数据传输非常重要。
- 安全监控与合规性
- 审计与合规性检查:BPF程序可以用于监视和记录网络操作,以确保符合法规和企业政策。这对于满足合规性要求并避免数据泄露至关重要。
- 异常行为分析:通过收集网络操作数据,BPF可以帮助识别异常行为模式,例如未授权访问和数据泄露迹象。及时检测这些行为可以有效防范内部和外部威胁。
- 恶意软件与病毒防护
- 恶意软件检测 :BPF能够检测网络流量中的已知恶意软件特征,阻止恶意软件传播和感染系统[^1^]。这对企业网络尤其重要,以避免潜在的经济损失。
- 病毒过滤:利用BPF程序,可以在邮件网关或网络边缘进行病毒过滤,防止病毒附件进入企业网络。
- 应用层安全
- Web应用防火墙:BPF技术可以用于构建高效的Web应用防火墙(WAF),保护Web应用程序免受SQL注入、跨站脚本攻击等常见攻击。
- API速率限制:通过BPF实现API调用的速率限制,可以防止滥用API服务,保障后端服务的可用性和安全性。
- 云原生环境安全
- 容器网络过滤 :在容器化环境中,BPF可以用于容器间网络的过滤和监控,确保容器之间安全隔离[^3^]。这对于部署在云原生环境中的微服务架构尤为重要。
- Service Mesh安全控制:BPF技术可以与Service Mesh技术结合,提供跨服务通信的安全控制和策略管理,确保服务之间通信的安全性。
- 零信任网络架构
- 动态访问控制:采用BPF技术可以实现基于策略的动态访问控制,确保只有经过验证和授权的流量才能访问敏感资源。
- 微分段与访问策略:BPF可以在网络层面实现微分段技术,精细控制不同网络段之间的访问权限,实现更加细粒度的安全控制。
- 事故响应与取证
- 快速响应:当网络安全事件发生时,BPF程序可以快速响应,隔离受影响的系统和服务,防止安全事件扩散。
- 取证分析:BPF捕获的网络数据包可以用于事后取证分析,帮助追踪攻击者的行为和手段,改进现有的安全防护措施。
- 漏洞扫描与评估
- 自动化漏洞扫描:通过BPF技术,可以实现对网络服务的自动化漏洞扫描,及时发现并修复潜在的安全漏洞。
- 安全评估工具:BPF可以用作安全评估工具,评估网络配置和系统设置的安全性,提供改进建议以增强整体网络安全。
BPF过滤器在数据包嗅探和过滤中的应用(C/C++代码实现)
用C编写的嗅探器程序,允许您在指定接口上以混杂模式捕获和过滤数据包。该程序使用libpcap库提供使用BPF过滤器嗅探和过滤数据包的支持。此外,该程序还支持读取预捕获的pcap转储,并根据指定的字符串过滤有效载荷。
c
...
void store_hex_ascii_line(const u_char *payload, int len, int offset)
{
int i;
const u_char *ch;
ch = payload;
for(i = 0; i < len; i++) {
if (isprint(*ch))
pload_buff[idx++] = *ch;
ch++;
}
return;
}
void store_payload(const u_char *payload, int len)
{
...
for ( ;; ) {
line_len = line_width % len_rem;
store_hex_ascii_line(ch, line_len, offset);
len_rem = len_rem - line_len;
ch = ch + line_len;
offset = offset + line_width;
if (len_rem <= line_width) {
store_hex_ascii_line(ch, len_rem, offset);
break;
}
}
return;
}
void print_ether_type(u_short ether_type, u_char *ether_dhost, u_char *ether_shost){
for(int i = 0; i < 6; i++){
printf("%02x", ether_shost[i]);
if (i <= 4)
printf(":");
else
printf(" > ");
}
for(int i = 0; i < 6; i++){
printf("%02x", ether_dhost[i]);
if (i <= 4)
printf(":");
else{
printf(" type 0x%03hx", ntohs(ether_type));
}
}
return;
}
void packet_handler(u_char *args, const struct pcap_pkthdr *header, const u_char *packet){
const struct sniff_ethernet *ethernet;
const struct sniff_ip *ip;
const struct sniff_tcp *tcp;
const struct sniff_udp *udp;
const struct icmp *icmphd;
u_char *payload;
char *type;
int ip_size, tcp_size, udp_size, pload_size;
int src_port, dst_port;
bpf_int32 *packet_size;
char src_ip[16], dst_ip[16];
ethernet = (struct sniff_ethernet*)(packet);
time_t timer;
char buffer[26];
// 打印捕获时间
const time_t *pkt_time = (time_t *)(&header->ts.tv_sec);
bpf_int32 *micro = (bpf_int32 *)&header->ts.tv_usec;
strftime(buffer, 26, "%Y-%m-%d %H:%M:%S", localtime(pkt_time));
packet_size = (bpf_int32*)&header->len;
uint16_t p_type = ntohs(ethernet->ether_type);
if (p_type == ETHERTYPE_ARP){
const struct sniff_arp *arp;
arp = (struct sniff_arp*)(packet + ETHER_SIZE);
snprintf (dst_ip, 16, "%d.%d.%d.%d",
arp->arp_dip[0], arp->arp_dip[1], arp->arp_dip[2], arp->arp_dip[3]);
snprintf (src_ip, 16, "%d.%d.%d.%d",
arp->arp_sip[0], arp->arp_sip[1], arp->arp_sip[2], arp->arp_sip[3]);
printf("%s.%06d ", buffer, *micro);
printf (" len %d ARP, Request who-has %s tell %s\n", *packet_size - ETHER_SIZE, dst_ip, src_ip);
}
else if (p_type == ETHERTYPE_IP){
ip = (struct sniff_ip*)(packet+ETHER_SIZE);
ip_size = IP_HL(ip)*4;
if (ip_size < 20)
return;
strcpy(src_ip, inet_ntoa(ip->ip_src));
strcpy(dst_ip, inet_ntoa(ip->ip_dst));
switch(ip->ip_p){
case IPPROTO_TCP:
type = "TCP";
tcp = (struct sniff_tcp*)(packet + ETHER_SIZE + ip_size);
tcp_size = TH_OFF(tcp)*4;
if (tcp_size < 20){
return;
}
src_port = ntohs(tcp->th_sport);
dst_port = ntohs(tcp->th_dport);
payload = (u_char *)(packet + ETHER_SIZE + ip_size + tcp_size);
pload_size = ntohs(ip->ip_len) - (ip_size + tcp_size);
if (sflag == 1 && sload){
if (pload_size == 0)
break;
else{
store_payload(payload, pload_size);
if (strstr((const char *)pload_buff, sload) == NULL){
idx = 0;
memset(&pload_buff, 0, sizeof(pload_buff));
return;
}
}
}
printf("%s.%06d ", buffer, *micro);
print_ether_type(ethernet->ether_type, (u_char *)ethernet->ether_dhost, (u_char *)ethernet->ether_shost);
printf(" len %d %s:%d > %s:%d %s\n", *packet_size - ETHER_SIZE, src_ip, src_port, dst_ip, dst_port, type);
print_payload(payload, pload_size);
printf("\n");
break;
case IPPROTO_UDP:
udp = (struct sniff_udp*)(packet + ETHER_SIZE + ip_size);
udp_size = (ntohs(udp->len));
if (udp_size < 8)
return;
type = "UDP";
src_port = ntohs(udp->udp_sport);
dst_port = ntohs(udp->udp_dport);
payload = (u_char *)(packet + ETHER_SIZE + ip_size + 8);
pload_size = ntohs(ip->ip_len) - (ip_size + 8);
if (sflag == 1 && sload){
if (pload_size == 0)
return;
else{
store_payload(payload, pload_size);
if (strstr((const char *)pload_buff, sload) == NULL){
idx = 0;
memset(&pload_buff, 0, sizeof(pload_buff));
return;
}
}
}
printf("%s.%06d ", buffer, *micro);
print_ether_type(ethernet->ether_type, (u_char *)ethernet->ether_dhost, (u_char *)ethernet->ether_shost);
printf(" len %d %s:%d > %s:%d %s\n", udp_size, src_ip, src_port, dst_ip, dst_port, type);
print_payload(payload, pload_size);
printf("\n");
break;
case IPPROTO_ICMP:
type = "ICMP";
printf("%s.%06d ", buffer, *micro);
icmphd = (struct icmp*)(packet + ETHER_SIZE + ip_size);
unsigned short id, seq;
memcpy(&id, (u_char*)icmphd+4, 2);
memcpy(&seq, (u_char*)icmphd+6, 2);
char *type;
payload = (u_char *)(packet + ETHER_SIZE + ip_size + 8);
pload_size = ntohs(ip->ip_len) - (ip_size + 8);
if (sflag == 1 && sload){
if (pload_size == 0)
return;
else{
store_payload(payload, pload_size);
if (strstr((const char *)pload_buff, sload) == NULL){
idx = 0;
memset(&pload_buff, 0, sizeof(pload_buff));
return;
}
}
}
if (icmphd->icmp_type == 8)
type = "request";
else if (icmphd->icmp_type == 0)
type = "reply";
if (icmphd->icmp_type == 3)
type = "unreachable";
printf("IP %s > %s: ICMP echo %s ,id %d, seq %d\n", src_ip, dst_ip, type, ntohs(id), ntohs(seq));
print_payload(payload, pload_size);
printf("\n");
break;
case IPPROTO_IGMP:
type = "IGMP";
printf("%s.%06d ", buffer, *micro);
print_ether_type(ethernet->ether_type, (u_char *)ethernet->ether_dhost, (u_char *)ethernet->ether_shost);
printf(" IP %s > %s: IGMP\n", src_ip, dst_ip);
break;
default:
type = "Unknown Protocol";
printf("%s.%06d ", buffer, *micro);
print_ether_type(ethernet->ether_type, (u_char *)ethernet->ether_dhost, (u_char *)ethernet->ether_shost);
printf(" IP %s > %s type %s\n", src_ip, dst_ip, type);
break;
}
// printf("\n");
}
idx = 0;
memset(&pload_buff, 0, sizeof(pload_buff));
return;
}
int main(int argc, char **argv){
...
while((opt = getopt(argc, argv, "i:r:s:")) != -1){
switch(opt)
{
case 'i':
iflag = 1;
iface = optarg;
break;
case 'r':
rflag = 1;
rfile = optarg;
break;
case 's':
sflag = 1;
sload = optarg;
break;
case ':':
fprintf(stderr, "requires an argument");
break;
case '?':
if (optopt == 'i' || optopt == 'r' || optopt == 's')
fprintf(stderr, "Option %c requires an argument\n", optopt);
else{
fprintf(stderr, "Invalid option %c to program\n", optopt);
}
return 1;
default:
exit(0);
}
}
// printf("%s %s %s %s", iface, rfile, sload, exp);
// printf("%d %d %d", iflag, rflag, sflag);
for(int i = optind; i < argc; i++){
strcat(exp, argv[i]);
printf("Expression: %s ", argv[i]);
}
if (strlen(exp) > 0){
if (pcap_lookupnet(iface, &net, &mask, errbuf) == -1){
fprintf(stderr, "Could not fetch net mask for device %s", iface);
net = 0;
mask = 0;
}
}
if (!iface && iflag == 0){
iface = pcap_lookupdev(errbuf);
if (iface == NULL) {
fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
exit(0);
}
}
if (rflag == 1 && rfile){
printf("Reading dump from file: %s\n", rfile);
handle = pcap_open_offline(rfile, errbuf);
if (!handle){
fprintf(stderr, "Error reading dump: %s\n", errbuf);
exit(0);
}
else if (pcap_datalink(handle) != DLT_EN10MB) {
fprintf(stderr, "Device %s doesn't provide Ethernet headers - not supported\n", iface);
exit(0);
}
}
else{
printf("Sniffing on device: %s\n", iface);
handle = pcap_open_live(iface, 1518, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "Couldn't open device %s: %s\n", iface, errbuf);
exit(0);
}
else if (pcap_datalink(handle) != DLT_EN10MB) {
fprintf(stderr, "Device %s doesn't provide Ethernet headers - not supported\n", iface);
exit(0);
}
}
/*使用`pcap_compile`函数编译过滤表达式,并使用`pcap_setfilter`函数将其应用于捕获的数据包*/
if (pcap_compile(handle, &filter, exp, 0, net) == -1){
fprintf(stderr, "Could not parse filter %s: %s\n", exp, pcap_geterr(handle));
exit(0);
}
if (pcap_setfilter(handle, &filter) == -1){
fprintf(stderr, "Could not install filter %s: %s\n", exp, pcap_geterr(handle));
exit(0);
}
...
}
If you need the complete source code, please add the WeChat number (c17865354792)
指定要搜索的字符串
筛选器表达式:
这段代码是一个网络数据包捕获和分析工具,通常用于网络监控和分析。它使用 libpcap
库来捕获网络流量,并解析不同的网络协议。下面是代码的主要部分及其功能的详细解释:
-
函数
store_hex_ascii_line
:- 将十六进制数据转换为ASCII,并存储到
pload_buff
中。如果字符是可打印的,就将其存储。
- 将十六进制数据转换为ASCII,并存储到
-
函数
store_payload
:- 处理数据包的负载部分,将其分解成多行,并调用
store_hex_ascii_line
来转换和存储。
- 处理数据包的负载部分,将其分解成多行,并调用
-
函数
print_ether_type
:- 打印以太网帧的源和目的MAC地址以及以太网类型。
-
函数
packet_handler
:- 这是
libpcap
回调函数,用于处理每个捕获的数据包。它解析以太网帧、IP、TCP、UDP和ICMP头部,并根据协议类型打印相关信息。
- 这是
-
主函数
main
:- 处理命令行参数,设置捕获设备、读取文件或实时捕获,并设置BPF(Berkeley Packet Filter)过滤表达式。
- 使用
pcap_loop
函数来循环处理每个捕获的数据包,使用packet_handler
作为回调函数。
-
命令行参数解析:
- 支持
-i
指定接口,-r
从文件中读取数据包,-s
指定要搜索的字符串。
- 支持
-
过滤表达式:
- 如果提供了过滤表达式,代码将编译并应用这个表达式来过滤捕获的数据包。
-
数据包捕获:
- 根据用户的选择,从指定的网络接口实时捕获数据包,或者从文件中读取。
总结
BPF过滤器为网络数据包的捕获和分析提供了一个强大而灵活的工具。通过定义过滤规则,用户可以有效地筛选出感兴趣的数据包,减少不必要的数据处理,提高分析效率。本文通过分析一个实际的代码示例,展示了BPF过滤器在数据包嗅探和过滤中的应用。随着网络技术的发展,BPF过滤器将继续在网络安全和性能监控中发挥重要作用。
We also undertake the development of program requirements here. If necessary, please follow the WeChat official account 【程序猿编码】and contact me