libpcap之数据分流

news2024/11/25 3:08:47

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

一、实验

  • 一个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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1171097.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Bat批量处理

一&#xff1a;创建文件夹 excel创建文件 复制出来新建文本文件 另存为bat 双击bat 二&#xff1a;批量移动文件 A列&#xff1a;获取的文件名列表 dir /b/o:n> original.txt B列&#xff1a;填充序号 C列公式&#xff1a;每隔9行增加1 INT((ROW(B1)-1)/9)1 D列公式&am…

CDN与WAF防火墙:强强联手,守护您的网站安全

随着互联网的普及&#xff0c;网站安全问题变得愈发重要。恶意攻击、数据泄露和服务中断等问题都可能给网站和用户带来严重损害。在保护网站免受这些威胁的过程中&#xff0c;内容分发网络&#xff08;CDN&#xff09;和Web应用程序防火墙&#xff08;WAF&#xff09;是两个强大…

C++算法 —— 贪心(2)

文章目录 1、柠檬水找零2、将数组和减半的最少操作次数3、最大数4、摆动序列5、最长递增子序列6、递增的三元子序列7、最长连续递增子序列 1、柠檬水找零 860. 柠檬水找零 如果一开始顾客给了10块&#xff0c;那就直接结束。给了5块那就收下。之后每一个位置&#xff0c;都需要…

进阶C语言-指针的进阶(三)

模拟实现qsort函数 &#x1f388;1.测试bubble_sort&#xff0c;排序整型数组&#x1f388;2测试bubble_sort&#xff0c;排序结构体数组 &#x1f4dd;关于qsort函数&#xff0c;我们可以先去cpluplus网站上面了解一下&#xff1a; //1.排序整型数组&#xff0c;两个整型可以…

Linux 上的轻量级浏览器

导读大多数 Linux 桌面环境中包含的基本图像查看器可能不足以满足你的需要。如果你想要一些更多的功能&#xff0c;但仍然希望它是轻量级的&#xff0c;那么看看这四个 Linux 桌面中的图像查看器&#xff0c;如果还不能满足你的需要&#xff0c;还有额外的选择。 当你需要的不…

竞赛选题 深度学习实现语义分割算法系统 - 机器视觉

文章目录 1 前言2 概念介绍2.1 什么是图像语义分割 3 条件随机场的深度学习模型3\. 1 多尺度特征融合 4 语义分割开发过程4.1 建立4.2 下载CamVid数据集4.3 加载CamVid图像4.4 加载CamVid像素标签图像 5 PyTorch 实现语义分割5.1 数据集准备5.2 训练基准模型5.3 损失函数5.4 归…

整理的一些Java细节问题

1. 为什么要有无参构造&#xff1f; 在 Java 中&#xff0c;如果一个类没有显式定义构造方法&#xff0c;编译器会自动生成一个默认的无参构造方法&#xff08;也称为默认构造方法&#xff09;。无参构造方法是一个没有任何参数的构造方法。 无参构造方法的存在有几个重要原因…

【vscode输出中文乱码】

vscode输出中文乱码为一个个的问号。 这个链接亲测有用 win11对应的界面在这里&#xff1a;

产品经理入门学习(二):产品经理问题思考维度

参考引用 黑马-产品经理入门基础课程 1. 抓住核心用户 1.1 为什么要抓住核心用户 什么是用户&#xff1f; 所有和产品有关系的群体就是用户&#xff0c;他们是一群既有共性&#xff0c;又有差异的群体组合 做产品为什么要了解用户&#xff1f; 了解用户的付费点、更好的优化产…

文件同步工具推荐:挑选高效实用的工具大揭秘

随着工作的累积&#xff0c;会持续产出大量电子资料和文件。如何妥善管理这些文件资料&#xff0c;成了一个问题。有需求就有市场&#xff0c;当下市场上也有很多文件同步工具。 有什么好用的文件同步工具&#xff1f; Zoho WorkDrive 同步网盘就是一款好用的文件同步工具&am…

Leetcode—199.二叉树的右视图【中等】

2023每日刷题&#xff08;十九&#xff09; Leetcode—199.二叉树的右视图 深度优先遍历实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(…

【数据结构】冒泡排序

冒泡排序 前言冒泡排序运行图例算法实现基本思路算法实现步骤算法码源详解冒泡排序效率分析&#xff08;一&#xff09;时间复杂度——O&#xff08;N^2&#xff09;&#xff08;二&#xff09;空间复杂度——O&#xff08;1&#xff09;&#xff08;三&#xff09;稳定性&…

人工智能基础_机器学习018_手写代码实现_MBGD小批量梯度下降---人工智能工作笔记0058

然后我们继续来看这里的小批量梯度下降,小批量梯度下降,其实就是 用少量的样本数据,进行梯度下降,上面是公式 然后我们来看代码 import numpy as np 导入数学计算包 #X,y创建数据集X=np.random.rand(100,1) x是100行1列 w,b=np.random.randint(1,10,size=2) 然后获取w和截距…

干货分享:10个行业可视化大屏模板(附 Python 源码)

大家好&#xff0c;数据大屏是一种用于展示和分析数据的可视化工具&#xff0c;通常用于监控、分析和报告数据。大屏可以帮助组织更好地理解和管理其数据&#xff0c;支持数据驱动决策&#xff0c;提高业务效率和决策的质量。 本文的所有大屏都是基于Python开发&#xff0c;因…

根据一个类型 获取该类型的 特殊判断 优雅写法

需求&#xff1a;一个统计接口&#xff0c;时间类型参数有以下&#xff1a;今日、近七天、近三十日等 如果我要查询的话&#xff0c;SQL 里的条件必定是一个时间范围&#xff0c;所以就需要根据类型来算好这个时间范围&#xff0c;所以可以写成下面这样。 到时候直接就是 获取…

前端vue,后端springboot。如何防止未登录的用户直接浏览器输入地址访问

前端&#xff0c;使用Vue框架来实现前端路由拦截&#xff1a; 设置需要登录校验的页面&#xff1a; 登录成功后&#xff0c;去设置LocalStorage里面的IsLogin为true:

[LeetCode]-链表中倒数第k个结点-CM11 链表分割-LCR 027. 回文链表

目录 链表中倒数第k个结点 题目 思路 代码 CM11 链表分割 题目 思路 代码 LCR 027.回文链表 题目 思路 代码 链表中倒数第k个结点 链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId…

Web3游戏的十字路口:沿用传统IP还是另起炉灶?

人们经常问我对 Web3 游戏有什么看法。因此&#xff0c;我想以书面形式概述一下我目前的想法。 让我先澄清一下&#xff1a;我不是专家。这不是一篇深入探讨游戏世界精细指标如 MAU 或 D14 等的全面分析。请把这看作是我根据个人交流和研究&#xff0c;这反映我在游戏领域关注…

学习Opencv(蝴蝶书/C++)相关——1. 前言 和 第1章.概述

文章目录 1. 整体架构1.1 OpenCV3.01.2 Opencv4.xX. Opencv cheatsheet(小抄)1. 整体架构 1.1 OpenCV3.0 对于Opencv3.x版本,网上最常见的图,图自OpenCV Tutorial-Itseez 现在已经不是500+的算法了,而是2500+,详见:About

喜报|英码科技荣登“广州首届百家新锐企业名单”、“2022年度中国好技术项目库名单”榜单

近日&#xff0c;英码科技喜报连连&#xff0c;在刚刚公布的2022年度“中国好技术”项目库入选名单和广州首届百家新锐企业名单中&#xff0c;英码科技凭借出色的技术创新能力和优秀的企业竞争力荣登榜单。 2022年度“中国好技术” 近期&#xff0c;2022年度“中国好技术”征集…