libpcap之数据分流

2023-11-05 09:44
文章标签 数据 分流 libpcap

本文主要是介绍libpcap之数据分流,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

当前系统都是多核系统,为了充分利用多核优势,数据包可以在底层就进行分流,可以通过多线程/多进程对同一个接口的数据进行并行的处理。

一、实验

  • 一个server/client程序
  • 一个简单的抓包程序,抓取server/client之间的通信数据

在这里插入图片描述

1.1 server.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>void
Usage() {printf("ip port\n");
}int
start_srv(const char *ip, int port) {int ret = 0;int sockfd;sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket:");ret = -1;goto out;}struct sockaddr_in srv;srv.sin_family = AF_INET;srv.sin_port = htons(port);inet_pton(AF_INET, ip, &srv.sin_addr.s_addr);socklen_t len = sizeof(srv);if (bind(sockfd, (const struct sockaddr *) &srv, len)) {perror("bind:");ret = -2;goto out;}if (listen(sockfd, 5)) {perror("listen:");ret = -3;goto out;}int cli_fd = accept(sockfd, NULL, NULL);if (cli_fd < 0) {perror("accept:");ret = -4;goto out;}char buf[128] = {0};read(cli_fd, buf, sizeof(buf)-1);sleep(1);memcpy(buf, "hello world", sizeof("hello world"));write(cli_fd, buf, strlen(buf));out:if (sockfd > 0) {close(sockfd);}return ret;
}int 
main(int argc, char **argv) {if (argc < 3) {Usage();exit(1);}start_srv(argv[1], atoi(argv[2]));return 0;
}

1.2 client.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>int
start_cli(const char *remote_ip, int port) {int ret = 0;int sockfd;sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket:");ret = -1;goto out;}struct sockaddr_in srv;srv.sin_family = AF_INET;srv.sin_port = htons(port);inet_pton(AF_INET, remote_ip, &srv.sin_addr.s_addr);socklen_t len = sizeof(srv);if (connect(sockfd, (const struct sockaddr *) &srv, len)) {perror("connect:");ret = -2;goto out;}char buf[128];snprintf(buf, sizeof(buf), "I'm client.");write(sockfd, buf, strlen(buf));read(sockfd, buf, sizeof(buf));out:if (sockfd > 0) {close(sockfd);}return ret;
}void
Usage() {printf("remote_ip port\n");
}int
main(int argc, char **argv) {if (argc < 3) {Usage();exit(1);}start_cli(argv[1], atoi(argv[2]));return 0;
}

1.3 fanouttest.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pcap.h>
#include <pcap-int.h>
#include <pcap/bpf.h>void 
Usage() {printf("-i device\n");printf("-w filename\n");printf("-f filter\n");
}int recv_pkt = 0;void 
handler_cb(u_char *priv, const struct pcap_pkthdr *h, const u_char *pkt) {if (priv) {pcap_dump(priv, h, pkt);} else {printf("recv pkt length %u\n", h->caplen);}++recv_pkt;
}pcap_t *handle = NULL;void
sig_handler(int num) {if (handle) {pcap_breakloop(handle);}
}int
fanout_test(const char *device, const char *filter, const char *filename) {signal(SIGUSR1, sig_handler);pcap_dumper_t *dumper = NULL;struct bpf_program bpf = {0};int ret = 0;char errbuf[PCAP_ERRBUF_SIZE];handle = pcap_open_live(device, 65535, 1, 1000, errbuf);if (!handle) {printf("open %s failed. %s\n", device, errbuf);ret = -1;goto out;}if (filter) {bpf_u_int32 localnet, netmask;if (pcap_lookupnet(device, &localnet, &netmask, errbuf)) {localnet = 0;netmask = 0;printf("lookupnet err: %s\n", errbuf);}if (pcap_compile(handle, &bpf, filter, 1, netmask)) {printf("compile %s failed. %s\n", filter, pcap_geterr(handle));ret = -2;goto out;}if (pcap_setfilter(handle, &bpf)) {printf("set filter failed. %s\n", pcap_geterr(handle));ret = -3;goto out;}}if (filename) {dumper = pcap_dump_open(handle, filename);if (!dumper) {printf("dump open %s failed. %s", filename, pcap_geterr(handle));ret = -4;goto out;}}pcap_loop(handle, -1, handler_cb, (u_char *)dumper);printf("recv %d packet\n", recv_pkt);out:if (dumper) {pcap_dump_close(dumper);}if (handle) {pcap_close(handle);}pcap_freecode(&bpf);return ret;
}int 
main(int argc, char **argv) {char *device = NULL;char *save_file = NULL;char *filter = NULL;int opt;while ((opt = getopt(argc, argv, "i:w:f:")) != -1) {switch (opt) {case 'i':device = optarg;break;case 'w':save_file = optarg;break;case 'f':filter = optarg;break;default:Usage();exit(1);break;}}if (!device) {Usage();exit (2);}fanout_test(device, filter, save_file);return 0;
}

1.4 实验过程

复用libpcap编译结构,直接在testprogs目录增加源码文件。
testprogs/Makefile
testprogs/fanouttest.c

拷贝获取数据

在这里插入图片描述
在这里插入图片描述

gcc server.c -o srv
gcc client.c -o cli

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以从结果看,数据确实拷贝了两份,每个文件都是完整的数据。

分流获取数据

在这里插入图片描述

在函数中添加setsockopt设置相关属性。

...
#include <errno.h>
#include <string.h>
#include <linux/if_packet.h>...int
fanout_test(const char *device, const char *filter, const char *filename) {...int val;int id = 0;val = (id << 16) | PACKET_FANOUT_QM;if (setsockopt(handle->fd, SOL_PACKET, PACKET_FANOUT, &val, sizeof(val))) {printf("set fanout failed. %s \n", strerror(errno));}pcap_loop(handle, -1, handler_cb, (u_char *)dumper);printf("recv %d packet\n", recv_pkt);
...
}

在这里插入图片描述
从结果看,左边抓取到20个包,右边抓取到10个包,数据确实被分流了,没有完全复制。

二、原理


static int
packet_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen)
{struct sock *sk = sock->sk;struct packet_sock *po = pkt_sk(sk);int ret;if (level != SOL_PACKET)return -ENOPROTOOPT;switch (optname) {...case PACKET_FANOUT:{int val;if (optlen != sizeof(val))return -EINVAL;if (copy_from_user(&val, optval, sizeof(val)))return -EFAULT;return fanout_add(sk, val & 0xffff, val >> 16);}...default:return -ENOPROTOOPT;}
}

在这里插入图片描述
在这里插入图片描述

static int fanout_add(struct sock *sk, u16 id, u16 type_flags)
{struct packet_rollover *rollover = NULL;struct packet_sock *po = pkt_sk(sk);struct packet_fanout *f, *match;u8 type = type_flags & 0xff;u8 flags = type_flags >> 8;int err;switch (type) {case PACKET_FANOUT_ROLLOVER:if (type_flags & PACKET_FANOUT_FLAG_ROLLOVER)return -EINVAL;case PACKET_FANOUT_HASH:case PACKET_FANOUT_LB:case PACKET_FANOUT_CPU:case PACKET_FANOUT_RND:case PACKET_FANOUT_QM:case PACKET_FANOUT_CBPF:case PACKET_FANOUT_EBPF:break;default:return -EINVAL;}mutex_lock(&fanout_mutex);err = -EALREADY;if (po->fanout)goto out;if (type == PACKET_FANOUT_ROLLOVER ||(type_flags & PACKET_FANOUT_FLAG_ROLLOVER)) {err = -ENOMEM;rollover = kzalloc(sizeof(*rollover), GFP_KERNEL);if (!rollover)goto out;atomic_long_set(&rollover->num, 0);atomic_long_set(&rollover->num_huge, 0);atomic_long_set(&rollover->num_failed, 0);}if (type_flags & PACKET_FANOUT_FLAG_UNIQUEID) {if (id != 0) {err = -EINVAL;goto out;}if (!fanout_find_new_id(sk, &id)) {err = -ENOMEM;goto out;}/* ephemeral flag for the first socket in the group: drop it */flags &= ~(PACKET_FANOUT_FLAG_UNIQUEID >> 8);}match = NULL;list_for_each_entry(f, &fanout_list, list) {if (f->id == id &&read_pnet(&f->net) == sock_net(sk)) {match = f;break;}}err = -EINVAL;if (match && match->flags != flags)goto out;if (!match) {err = -ENOMEM;match = kzalloc(sizeof(*match), GFP_KERNEL);if (!match)goto out;write_pnet(&match->net, sock_net(sk));match->id = id;match->type = type;match->flags = flags;INIT_LIST_HEAD(&match->list);spin_lock_init(&match->lock);refcount_set(&match->sk_ref, 0);fanout_init_data(match);match->prot_hook.type = po->prot_hook.type;match->prot_hook.dev = po->prot_hook.dev;match->prot_hook.func = packet_rcv_fanout;match->prot_hook.af_packet_priv = match;match->prot_hook.id_match = match_fanout_group;list_add(&match->list, &fanout_list);}err = -EINVAL;spin_lock(&po->bind_lock);if (po->running &&match->type == type &&match->prot_hook.type == po->prot_hook.type &&match->prot_hook.dev == po->prot_hook.dev) {err = -ENOSPC;if (refcount_read(&match->sk_ref) < PACKET_FANOUT_MAX) {__dev_remove_pack(&po->prot_hook);po->fanout = match;po->rollover = rollover;rollover = NULL;refcount_set(&match->sk_ref, refcount_read(&match->sk_ref) + 1);__fanout_link(sk, po);err = 0;}}spin_unlock(&po->bind_lock);if (err && !refcount_read(&match->sk_ref)) {list_del(&match->list);kfree(match);}out:kfree(rollover);mutex_unlock(&fanout_mutex);return err;
}

通过将入口函数tpacket_rcv替换成packet_rcv_fanout,并且将相同的抓包方式放在fanout中的一个数据中,最后将此fanout挂在一个全局列表fanout_list中。
并且将po从ptype_all列表中剔除掉,最后在ptype_all中只保留一个即可,这样数据就只有一个入口,然后在到packet_rcv_fanout中进行分流处理。

static int packet_rcv_fanout(struct sk_buff *skb, struct net_device *dev,struct packet_type *pt, struct net_device *orig_dev)
{struct packet_fanout *f = pt->af_packet_priv;unsigned int num = READ_ONCE(f->num_members);struct net *net = read_pnet(&f->net);struct packet_sock *po;unsigned int idx;if (!net_eq(dev_net(dev), net) || !num) {kfree_skb(skb);return 0;}if (fanout_has_flag(f, PACKET_FANOUT_FLAG_DEFRAG)) {skb = ip_check_defrag(net, skb, IP_DEFRAG_AF_PACKET);if (!skb)return 0;}switch (f->type) {case PACKET_FANOUT_HASH:default:idx = fanout_demux_hash(f, skb, num);break;case PACKET_FANOUT_LB:idx = fanout_demux_lb(f, skb, num);break;case PACKET_FANOUT_CPU:idx = fanout_demux_cpu(f, skb, num);break;case PACKET_FANOUT_RND:idx = fanout_demux_rnd(f, skb, num);break;case PACKET_FANOUT_QM:idx = fanout_demux_qm(f, skb, num);break;case PACKET_FANOUT_ROLLOVER:idx = fanout_demux_rollover(f, skb, 0, false, num);break;case PACKET_FANOUT_CBPF:case PACKET_FANOUT_EBPF:idx = fanout_demux_bpf(f, skb, num);break;}if (fanout_has_flag(f, PACKET_FANOUT_FLAG_ROLLOVER))idx = fanout_demux_rollover(f, skb, idx, true, num);po = pkt_sk(f->arr[idx]);return po->prot_hook.func(skb, dev, &po->prot_hook, orig_dev);
}

packet_rcv_fanout 进入后,根据不同的算法进行选择一个po,然后调用相应的func(tpacket_rcv)

这篇关于libpcap之数据分流的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/348998

相关文章

SpringBoot集成EasyExcel实现百万级别的数据导入导出实践指南

《SpringBoot集成EasyExcel实现百万级别的数据导入导出实践指南》本文将基于开源项目springboot-easyexcel-batch进行解析与扩展,手把手教大家如何在SpringBo... 目录项目结构概览核心依赖百万级导出实战场景核心代码效果百万级导入实战场景监听器和Service(核心

使用Python开发一个Ditto剪贴板数据导出工具

《使用Python开发一个Ditto剪贴板数据导出工具》在日常工作中,我们经常需要处理大量的剪贴板数据,下面将介绍如何使用Python的wxPython库开发一个图形化工具,实现从Ditto数据库中读... 目录前言运行结果项目需求分析技术选型核心功能实现1. Ditto数据库结构分析2. 数据库自动定位3

pandas数据的合并concat()和merge()方式

《pandas数据的合并concat()和merge()方式》Pandas中concat沿轴合并数据框(行或列),merge基于键连接(内/外/左/右),concat用于纵向或横向拼接,merge用于... 目录concat() 轴向连接合并(1) join='outer',axis=0(2)join='o

批量导入txt数据到的redis过程

《批量导入txt数据到的redis过程》用户通过将Redis命令逐行写入txt文件,利用管道模式运行客户端,成功执行批量删除以Product*匹配的Key操作,提高了数据清理效率... 目录批量导入txt数据到Redisjs把redis命令按一条 一行写到txt中管道命令运行redis客户端成功了批量删除k

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

解决pandas无法读取csv文件数据的问题

《解决pandas无法读取csv文件数据的问题》本文讲述作者用Pandas读取CSV文件时因参数设置不当导致数据错位,通过调整delimiter和on_bad_lines参数最终解决问题,并强调正确参... 目录一、前言二、问题复现1. 问题2. 通过 on_bad_lines=‘warn’ 跳过异常数据3

C#监听txt文档获取新数据方式

《C#监听txt文档获取新数据方式》文章介绍通过监听txt文件获取最新数据,并实现开机自启动、禁用窗口关闭按钮、阻止Ctrl+C中断及防止程序退出等功能,代码整合于主函数中,供参考学习... 目录前言一、监听txt文档增加数据二、其他功能1. 设置开机自启动2. 禁止控制台窗口关闭按钮3. 阻止Ctrl +

java如何实现高并发场景下三级缓存的数据一致性

《java如何实现高并发场景下三级缓存的数据一致性》这篇文章主要为大家详细介绍了java如何实现高并发场景下三级缓存的数据一致性,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 下面代码是一个使用Java和Redisson实现的三级缓存服务,主要功能包括:1.缓存结构:本地缓存:使

在MySQL中实现冷热数据分离的方法及使用场景底层原理解析

《在MySQL中实现冷热数据分离的方法及使用场景底层原理解析》MySQL冷热数据分离通过分表/分区策略、数据归档和索引优化,将频繁访问的热数据与冷数据分开存储,提升查询效率并降低存储成本,适用于高并发... 目录实现冷热数据分离1. 分表策略2. 使用分区表3. 数据归档与迁移在mysql中实现冷热数据分

C#解析JSON数据全攻略指南

《C#解析JSON数据全攻略指南》这篇文章主要为大家详细介绍了使用C#解析JSON数据全攻略指南,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、为什么jsON是C#开发必修课?二、四步搞定网络JSON数据1. 获取数据 - HttpClient最佳实践2. 动态解析 - 快速