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

相关文章

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程

一文教你Python如何快速精准抓取网页数据

《一文教你Python如何快速精准抓取网页数据》这篇文章主要为大家详细介绍了如何利用Python实现快速精准抓取网页数据,文中的示例代码简洁易懂,具有一定的借鉴价值,有需要的小伙伴可以了解下... 目录1. 准备工作2. 基础爬虫实现3. 高级功能扩展3.1 抓取文章详情3.2 保存数据到文件4. 完整示例

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

python处理带有时区的日期和时间数据

《python处理带有时区的日期和时间数据》这篇文章主要为大家详细介绍了如何在Python中使用pytz库处理时区信息,包括获取当前UTC时间,转换为特定时区等,有需要的小伙伴可以参考一下... 目录时区基本信息python datetime使用timezonepandas处理时区数据知识延展时区基本信息

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll

Pandas统计每行数据中的空值的方法示例

《Pandas统计每行数据中的空值的方法示例》处理缺失数据(NaN值)是一个非常常见的问题,本文主要介绍了Pandas统计每行数据中的空值的方法示例,具有一定的参考价值,感兴趣的可以了解一下... 目录什么是空值?为什么要统计空值?准备工作创建示例数据统计每行空值数量进一步分析www.chinasem.cn处

如何使用 Python 读取 Excel 数据

《如何使用Python读取Excel数据》:本文主要介绍使用Python读取Excel数据的详细教程,通过pandas和openpyxl,你可以轻松读取Excel文件,并进行各种数据处理操... 目录使用 python 读取 Excel 数据的详细教程1. 安装必要的依赖2. 读取 Excel 文件3. 读

Spring 请求之传递 JSON 数据的操作方法

《Spring请求之传递JSON数据的操作方法》JSON就是一种数据格式,有自己的格式和语法,使用文本表示一个对象或数组的信息,因此JSON本质是字符串,主要负责在不同的语言中数据传递和交换,这... 目录jsON 概念JSON 语法JSON 的语法JSON 的两种结构JSON 字符串和 Java 对象互转

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类