libpcap获取数据包

news2024/11/18 17:35:53

一、用户空间

以Linux以及TPACKET_V3为例。
调用pcap_dispatch获取数据包,然后回调用户传递的数据包处理函数。
read_op实际调用的是pcap_read_linux_mmap_v3

// pcap.c
int
pcap_dispatch(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
{
	return (p->read_op(p, cnt, callback, user));
}

在这里插入图片描述

1.1 获取block

1.1.1 根据offset获取一个block

#define RING_GET_FRAME_AT(h, offset) (((union thdr **)h->buffer)[(offset)])
#define RING_GET_CURRENT_FRAME(h) RING_GET_FRAME_AT(h, h->offset)

h.raw = RING_GET_CURRENT_FRAME(handle);

1.1.2 判断当前block的状态

根据block_status值判断block的实际状态,主要关注两个值,
TP_STATUS_KERNEL - block正在内核使用,用户不能使用
TP_STATUS_USER - block已经由内核填充了数据,用户可以读取,内核不能使用

if (h.h3->hdr.bh1.block_status == TP_STATUS_KERNEL) {
	...
}

1.2 读取/处理数据

如果当前block的status为TP_STATUS_USER,则开始读取数据

...

//偏移到实际的数据包部分
handlep->current_packet = h.raw + h.h3->hdr.bh1.offset_to_first_pkt;
//当前block中数据包的个数
handlep->packets_left = h.h3->hdr.bh1.num_pkts;

while (packets_to_read-- && !handle->break_loop) {
			struct tpacket3_hdr* tp3_hdr = (struct tpacket3_hdr*) handlep->current_packet;
			ret = pcap_handle_packet_mmap(
					handle,
					callback,
					user,
					handlep->current_packet,
					tp3_hdr->tp_len,
					tp3_hdr->tp_mac,
					tp3_hdr->tp_snaplen,
					tp3_hdr->tp_sec,
					handle->opt.tstamp_precision == PCAP_TSTAMP_PRECISION_NANO ? tp3_hdr->tp_nsec : tp3_hdr->tp_nsec / 1000,
					VLAN_VALID(tp3_hdr, &tp3_hdr->hv1),
					tp3_hdr->hv1.tp_vlan_tci,
					VLAN_TPID(tp3_hdr, &tp3_hdr->hv1));
			...
			
			//移动到下一个包
			handlep->current_packet += tp3_hdr->tp_next_offset;
			handlep->packets_left--;
}
...

回调用户处理函数

/* handle a single memory mapped packet */
static int pcap_handle_packet_mmap(
		pcap_t *handle,
		pcap_handler callback,
		u_char *user,
		unsigned char *frame,
		unsigned int tp_len,
		unsigned int tp_mac,
		unsigned int tp_snaplen,
		unsigned int tp_sec,
		unsigned int tp_usec,
		int tp_vlan_tci_valid,
		__u16 tp_vlan_tci,
		__u16 tp_vlan_tpid)
{
	...
	
	/* pass the packet to the user */
	callback(user, &pcaphdr, bp);

	return 1;
}

1.3 "释放"当前block

当前block的数据包处理完成后,需要将当前block归还给内核,让内核可以继续写数据,只是将状态值设置为TP_STATUS_KERNEL即可。

if (handlep->packets_left <= 0) {
	h.h3->hdr.bh1.block_status = TP_STATUS_KERNEL;
	...
	/* next block */
	if (++handle->offset >= handle->cc)
		handle->offset = 0;

	handlep->current_packet = NULL;
}

1.4 等待数据

因为block是一个循环队列,只要当前block的状态是TP_STATUS_KERNEL则说明后面都没有数据,只能等待。
libpcap通过poll进行数据的等待,其中fd则是最开始创建的socket。

if (h.h3->hdr.bh1.block_status == TP_STATUS_KERNEL) {
	ret = pcap_wait_for_frames_mmap(handle);
	if (ret) {
		return ret;
	}
}
static int pcap_wait_for_frames_mmap(pcap_t *handle)
{
	struct pcap_linux *handlep = handle->priv;
	...
	struct pollfd pollinfo;
	int ret;

	pollinfo.fd = handle->fd;
	pollinfo.events = POLLIN;

	do {
		ret = poll(&pollinfo, 1, handlep->poll_timeout);
		
		...
	} while (ret < 0);

1.4.1 阻塞模式

默认是阻塞模式,并且TPACKET3下超时为-1(永不超时), 当没有流量时,将不会被唤醒。

static void
set_poll_timeout(struct pcap_linux *handlep) {
...
	if (handlep->tp_version == TPACKET_V3 && !broken_tpacket_v3)
			handlep->poll_timeout = -1;	/* block forever, let TPACKET_V3 wake us up */
		else
		...
}
如何提前唤醒?

在 libpcap-1.10.4之前无法提前唤醒,只能等待数据的到来,在新版本中增加了一个fd,专门用来提前唤醒。
https://github.com/the-tcpdump-group/libpcap/pull/741/commits/5c8b13d3e87542527ed9a3a79fb0f9b2edb74df1

  • 在创建handle时,同时创建了poll_breakloop_fd
pcap_t *
pcap_create_interface(const char *device, char *ebuf)
{
	pcap_t *handle;

	handle = PCAP_CREATE_COMMON(ebuf, struct pcap_linux);
	if (handle == NULL)
		return NULL;
	
	...
	
	struct pcap_linux *handlep = handle->priv;
	handlep->poll_breakloop_fd = eventfd(0, EFD_NONBLOCK);

	return handle;
}
  • 激活handle时设置对应的break_loop callback
static int
pcap_activate_linux(pcap_t *handle)
	...
	handle->breakloop_op = pcap_breakloop_linux;
	...
}
  • poll时将poll_breakloop_fd也监听
static int pcap_wait_for_frames_mmap(pcap_t *handle)
{
	struct pcap_linux *handlep = handle->priv;
	...
	struct pollfd pollinfo[2];
	int numpollinfo;
	pollinfo[0].fd = handle->fd;
	pollinfo[0].events = POLLIN;
	...
		pollinfo[1].fd = handlep->poll_breakloop_fd;
		pollinfo[1].events = POLLIN;
		numpollinfo = 2;
	...
  • 调用pcap_breakloop通知唤醒
void
pcap_breakloop(pcap_t *p)
{
	p->breakloop_op(p);
}
static void pcap_breakloop_linux(pcap_t *handle)
{
	pcap_breakloop_common(handle);
	struct pcap_linux *handlep = handle->priv;

	uint64_t value = 1;
	/* XXX - what if this fails? */
	if (handlep->poll_breakloop_fd != -1)
		(void)write(handlep->poll_breakloop_fd, &value, sizeof(value));
}
  • poll被poll_breakloop_fd唤醒
if (pollinfo[1].revents & POLLIN) {
	ssize_t nread;
	uint64_t value;

	nread = read(handlep->poll_breakloop_fd, &value,
	    sizeof(value));
	...

	if (handle->break_loop) {
		handle->break_loop = 0;
		return PCAP_ERROR_BREAK;
	}
}

1.4.2 非阻塞模式

没有数据时,立刻返回,通过如下API进行设置

int
pcap_setnonblock(pcap_t *p, int nonblock, char *errbuf)

二、内核空间

在这里插入图片描述

内核在接收到数据包时,将调用相应的处理函数进行处理

__netif_receive_skb_core()
	deliver_skb()
static inline int deliver_skb(struct sk_buff *skb,
			      struct packet_type *pt_prev,
			      struct net_device *orig_dev)
{
	...
	return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}

而pt_prev->func实际为在设置rx ring时设置的函数tpacket_rcv

2.1 判断是否有可用空间

如果没有空间了,则当前数据包被丢弃。

/* If we are flooded, just give up */
	if (__packet_rcv_has_room(po, skb) == ROOM_NONE) {
		atomic_inc(&po->tp_drops);
		goto drop_n_restore;
	}

2.2 获取一个可用的block

h.raw = packet_current_rx_frame(po, skb,
					TP_STATUS_KERNEL, (macoff+snaplen));

2.3 拷贝数据到block中

skb_copy_bits(skb, 0, h.raw + macoff, snaplen);

2.4 更新block属性

switch (po->tp_version) {
	...
	case TPACKET_V3:
		h.h3->tp_status |= status;
		h.h3->tp_len = skb->len;
		h.h3->tp_snaplen = snaplen;
		h.h3->tp_mac = macoff;
		h.h3->tp_net = netoff;
		h.h3->tp_sec  = ts.tv_sec;
		h.h3->tp_nsec = ts.tv_nsec;
		memset(h.h3->tp_padding, 0, sizeof(h.h3->tp_padding));
		hdrlen = sizeof(*h.h3);
		break;
	default:
		BUG();
	}

2.5 何时更新block状态

1. 当block写满时

在获取每次获取block时,将判断当前block是否有足够的空间写入当前的数据包

static void *packet_current_rx_frame(struct packet_sock *po,
					    struct sk_buff *skb,
					    int status, unsigned int len)
{
	char *curr = NULL;
	switch (po->tp_version) {
		...
	case TPACKET_V3:
		return __packet_lookup_frame_in_block(po, skb, len);
		...
	}
}
static void *__packet_lookup_frame_in_block(struct packet_sock *po,
					    struct sk_buff *skb,
					    unsigned int len
					    )
{
	...
	/* 空间足够,直接返回 */
	if (curr+TOTAL_PKT_LEN_INCL_ALIGN(len) < end) {
		prb_fill_curr_block(curr, pkc, pbd, len);
		return (void *)curr;
	}

空间不足时,将当前block 关闭(即将状态设置为TP_STATUS_USER),并通知socket fd有数据, 用户空间的poll则会被唤醒。

	/* Ok, close the current block */
	prb_retire_current_block(pkc, po, 0);
static void prb_retire_current_block(struct tpacket_kbdq_core *pkc,
		struct packet_sock *po, unsigned int status)
{
	...
		prb_close_block(pkc, pbd, po, status);
	...
}
static void prb_close_block(struct tpacket_kbdq_core *pkc1,
		struct tpacket_block_desc *pbd1,
		struct packet_sock *po, unsigned int stat)
{
	__u32 status = TP_STATUS_USER | stat;
...
	/* Flush the block */
	prb_flush_block(pkc1, pbd1, status);

	//通知socket 有数据,poll将被唤醒
	sk->sk_data_ready(sk);

	pkc1->kactive_blk_num = GET_NEXT_PRB_BLK_NUM(pkc1);
}

static void prb_flush_block(struct tpacket_kbdq_core *pkc1,
		struct tpacket_block_desc *pbd1, __u32 status)
{
	/* Now update the block status. */
	BLOCK_STATUS(pbd1) = status;
}

2. 当block超时时

当流量很小时,block一直都不会被写满,因此数据一直停留在block中,上层应用无法获取数据;因此增加了一个timer.

  • 在建立ring buf时初始化timer并设置超时处理函数
static void init_prb_bdqc(struct packet_sock *po,
			struct packet_ring_buffer *rb,
			struct pgv *pg_vec,
			union tpacket_req_u *req_u)
{
	...
	prb_setup_retire_blk_timer(po);
	...
}

static void prb_setup_retire_blk_timer(struct packet_sock *po)
{
	struct tpacket_kbdq_core *pkc;

	pkc = GET_PBDQC_FROM_RB(&po->rx_ring);
	timer_setup(&pkc->retire_blk_timer, prb_retire_rx_blk_timer_expired,
		    0);
	pkc->retire_blk_timer.expires = jiffies;
}
  • timer超时,调用回调函数,关闭当前block
static void prb_retire_rx_blk_timer_expired(struct timer_list *t)
{
	...
	if (pkc->last_kactive_blk_num == pkc->kactive_blk_num) {
		...
			prb_retire_current_block(pkc, po, TP_STATUS_BLK_TMO);
		...	
	}
...
}

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

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

相关文章

2023年【R1快开门式压力容器操作】考试题及R1快开门式压力容器操作模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 R1快开门式压力容器操作考试题是安全生产模拟考试一点通生成的&#xff0c;R1快开门式压力容器操作证模拟考试题库是根据R1快开门式压力容器操作最新版教材汇编出R1快开门式压力容器操作仿真模拟考试。2023年【R1快开…

什么是AUTOSAR ComStack,AUTOSAR架构中,CAN通信堆栈CAN Communication Stack介绍

AUTOSAR&#xff08;Automotive Open System Architecture&#xff09;ComStack指的是AUTOSAR架构中的通信堆栈。在AUTOSAR体系结构中&#xff0c;ComStack是指用于不同软件组件&#xff08;如应用软件、基础软件等&#xff09;之间进行通信的一组协议和服务。 在AUTOSAR架构中…

对于SOCKET套接字问题的若干认识

1. 首先大家应该知道Socket 编程吧 Socket套接字 分为 应用层套接字 数据链路层套接字&#xff08;也就是原始socket&#xff09; 1.流套接字(SOCK_STREAM) 流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送&#xff0c;并按顺序接…

精通Nginx(02)-Nginx安装

本文主要讲述Nginx在Linux下的yum安装方式。 Yum安装一般用root模式。 安装方式 在Linux下&#xff0c;Nginx安装有源码编译安装和yum(不同OS可能命令名字不一样)直接安装两种方式。两种方式优缺点如下&#xff1a; 源码编译安装 优点&#xff1a;灵活性高定制性强&#xff…

【Spring MVC】传递参数

前言&#xff1a; 访问不同路径就是在发送不同的请求&#xff0c;在发送请求时&#xff0c;可能会带有一些参数&#xff0c;所以Spring的请求主要是为了学习如何传递参数到后端以及后端如何接收。 在SpringMVC中使用RequestMapping来实现路由映射&#xff0c;也就是浏览器连接…

5G物联网关相较有线网关有哪些独特优势

5G为产业物联网应用带来了质的飞跃&#xff0c;5G技术实现更高速率、更低延迟和更大带宽&#xff0c;使得物联网能够接入更多数量的设备&#xff0c;实现更稳定、高效的连接和数据传输&#xff0c;在提高生产效率的同时&#xff0c;也进一步促进了物联网的应用发展和升级。 针对…

预约上门洗衣店洗鞋店管理软件;

洗衣店洗鞋店管理软件&#xff1b; 软件操作简单&#xff0c;专业管理进货、库存、销售、会员、利润统计和导购员提成。前台扫描销售和会员打折&#xff0c;会员充值消费&#xff0c;支持扫码支付&#xff0c;结账自动打印小票。手机可以查询库存和营业情况&#xff01; 干洗店…

PHP连接SQLServer echo输出中文汉字显示乱码解决方法

1、查询结果有中文会显示乱码。 解决方法一&#xff08;较简单&#xff0c;建议使用&#xff09;&#xff1a; 在php文件最开头写上&#xff1a; header(Content-type: text/html; charsetUTF8); // UTF8不行改成GBK试试&#xff0c;与你保存的格式匹配 <?php header(&q…

RecyclerView自定义LayoutManager从0到1实践

此前大部分涉及到 RecyclerView 页面的 LayoutManager基本上用系统提供的 LinearLayoutManager 、GridLayoutManager 就能解决&#xff0c;但在一些特殊场景上还是需要我们自定义 LayoutManager。之前基本上没有自己写过&#xff0c;在网上看各种源码各种文章&#xff0c;刚开始…

[MySQL]索引

目录 概念解释 作用/优点 缺点 适用场景 索引的创建,删除与查看 系统对索引的自动创建 索引建立的时机 索引存储的数据结构 选择B树的原因 B树的原理 查询流程 优点 B树 与B树的区别 优点 概念解释 索引就像是一本字典的目录,我们可以根据目录快速定位到我们想…

Java架构师前沿技术

目录 1 导学2 信息物理系统2.1CPS的体系架构2.2 CPS的技术体系3 人工智能4 机器人5 边缘计算6 数字李生体7 云计算7.1 云计算的部署模式8 大数据想学习架构师构建流程请跳转:Java架构师系统架构设计 1 导学 2 信息物理系统 信息物理系统(CPS)是控制系统、嵌入式系统的扩展与…

一张图讲清楚业务稳定性要如何做:SRE体系化稳定性方案

概述&#xff1a;作为一个SRE、运维工程师&#xff0c;当我们在治理系统稳定性时&#xff0c;方法有很多&#xff0c;但往往无从下手。本文以一张逻辑图的形式&#xff0c;为读者提供治理稳定性的体系化思路。 先上图&#xff1a; 1、治理目标 我们做稳定性的目标&#xff0c…

MySQL 基础学习笔记(二)

目录 1 约束1.1 约束概述1.2 非空约束1.3 唯一约束1.4 主键约束1.5 默认约束1.6 外键约束 2 数据库设计2.1 数据库设计概述2.2 表关系 3 多表查询3.1 多表查询概述3.2 内连接查询3.3 外连接查询3.4 子查询 4 事务4.1 事务概述4.2 四大特征 1 约束 1.1 约束概述 约束是作用于表…

Whatsapp企业号如何增粉?5个措施帮到你

收集粉丝关注的方法有很多种&#xff0c;本文将会介绍九种有效的方式&#xff0c;可以参考。 在传播信息的过程中&#xff0c;我们要需要把客户放在第一位&#xff0c;你能提供给客户什么&#xff0c;实用价值或情绪价格。给人的感觉真实吗&#xff1f;足够透明吗&#xff1f;…

[黑马程序员Pandas教程]——Pandas快速体验

目录&#xff1a; 为什么要使用Python做数据开发Python在数据开发领域的优势为什么要学习Pandas其他常用Python库介绍主要内容介绍Anaconda安装Anaconda的虚拟环境管理虚拟环境的作用可以通过Anaconda界面创建虚拟环境通过命令行创建虚拟环境通过Anaconda管理界面安装包也可以…

Linux C语言进阶-D2字符数组和字符串

字符数组&#xff1a;元素的数据类型为字符类型的数组 char c[10],ch[3][4]; 字符数组的初始化 逐个字符赋值,无\0 在下图中&#xff0c;剩余的会自动添加上\0&#xff0c;而在int中会自动添加0&#xff0c;和NULL其实是一个意思 用字符串常量有\0 字符数组长度计算 下图中&am…

你知道什么是 Ping 吗?

欢迎到我的博客浏览 胤凯 (oyto.github.io) 这次我们来看一下什么是 Ping 操作&#xff0c;以及它有什么用处&#xff0c;并且我们来动手实现一个简易版的 Ping 工具。 Ping 是什么&#xff1f; ​ ping 是一个计算机网络工具&#xff0c;通常用于测试网络连接的可达性和测…

服务器带宽忽然暴增,不停的触发告警

问题&#xff1a; 线上环境&#xff0c;服务器的外网下行带宽达到某个阈值&#xff0c;触发告警&#xff0c;查了下服务器的带宽监控信息&#xff0c;是从某个时间开始突然串上去的&#xff0c;然后监控图形非常有规律&#xff0c;都是每秒达到顶峰后&#xff0c;又立马下去了…

信息系统项目管理师教程 第四版【第9章-项目范围管理-思维导图】

信息系统项目管理师教程 第四版【第9章-项目范围管理-思维导图】 课本里章节里所有蓝色字体的思维导图

2023年云栖大会来啦!!(2022年就已经深受震撼)

2023云栖大会已经开始啦&#xff0c;让我们来回顾回顾去年的云栖大会吧。 云栖大会是中国阿里巴巴集团每年举办的一项技术盛会&#xff0c;前身可追溯到2009年的地方网站峰会&#xff0c;2011年演变为阿里云开发者大会&#xff0c;2015年正式更名为“云栖大会”&#xff0c;并且…