libpcap获取数据包

一、用户空间

以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.mzph.cn/news/126236.shtml

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

相关文章

钡铼技术助力ARM工控机在智慧交通中的创新应用

在交通运输领域&#xff0c;钡铼技术ARM工控机可以实现以下功能&#xff1a; 实时监控和管理&#xff1a;利用钡铼技术ARM工控机&#xff0c;可以对交通运输中的车辆、船只、飞机等进行实时监测和管理&#xff0c;帮助调度员提高车辆调度和路线规划的准确性和效率。 安全保障&…

Vue 3.0中Treeshaking特性是什么?

一、是什么 Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术&#xff0c;专业术语叫 Dead code elimination 简单来讲&#xff0c;就是在保持代码运行结果不变的前提下&#xff0c;去除无用的代码 如果把代码打包比作制作蛋糕&#xff0c;传统的方式是把鸡…

js实现容器之间交换

&#x1f525;博客主页&#xff1a; 破浪前进 &#x1f516;系列专栏&#xff1a; Vue、React、PHP ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ JavaScript是一种非常流行和常用的编程语言&#xff0c;它在web开发中起着至关重要的作用&#xff0c;在实现网页动态交互、数据…

实时检测并识别视频中的汽车车牌

对于基于摄像头监控的安全系统来说,识别汽车牌照是一项非常重要的任务。我们可以使用一些计算机视觉技术从图像中提取车牌,然后我们可以使用光学字符识别来识别车牌号码。在这里,我将引导您完成此任务的整个过程。 要求: import cv2import numpy as npfrom skimage impor…

黑马 小兔鲜儿 uniapp 小程序开发- 商品详情模块- day05

黑马 小兔鲜儿 uniapp 小程序开发- 分类模块- day04-CSDN博客 小兔鲜儿 - 商品详情(登录前)-day05 商品详情页分为两部分讲解&#xff1a; 登录前&#xff1a;展示商品信息&#xff0c;轮播图交互&#xff08;当前模块&#xff09;登录后&#xff1a;加入购物车&#xff0c;立…

Xcode中如何操作Git

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

[云原生案例1.] 构建LNMP架构并运行Wordpress个人博客平台

文章目录 1. 当前需求2. 前置准备3. 搭建过程3.1 创建自定义网络3.2 部署并配置nginx3.2.1 创建工作目录并上传相关软件包3.2.2 解压缩相关软件包3.2.3 编写Dockerfile文件3.2.4 编写nginx.conf文件3.2.5 创建nginx镜像3.2.6 运行容器 3.3 部署并配置mysql3.3.1 创建工作目录3.…

Python机器学习基础(二)---数据可视化

一.简单图形生成 1.Pandas生成折线图 import pandas as pd import numpy as np from matplotlib import pyplot #生成10行4列 标准正态分布的数据 df pd.DataFrame(np.random.randn(10,4),indexpd.date_range(1/1/2000,periods10), columnslist(ABCD)) df.plot() print(np.r…

分布式理论和分布式锁知识点总结

文章目录 (一) 分布式理论算法和协议1&#xff09;CAP理论总结 2&#xff09;BASE理论BASE 理论的核心思想基本可用软状态最终一致性 3&#xff09;Paxos算法Basic Paxos 算法4&#xff09; Raft算法1 拜占庭将军 5&#xff09;Gossip协议 (二) 分布式锁分布式锁应该具备哪些条…

89 柱状图中最大的矩形

柱状图中最大的矩形 类似接雨水&#xff08;反过来&#xff0c;相当于找接雨水最少的一段&#xff09;题解1 暴力搜索&#xff08;超时&#xff09; O ( N 2 ) O(N^2) O(N2)另一种 题解2 单调栈【重点学习】常数优化 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的…

看完这个,别说你还找不到免费好用的配音软件

有很多小伙伴还在找配音工具&#xff0c;今天就给大家一次性分享四款免费好用的配音工具&#xff0c;每一个都经过测试&#xff0c;并且是我们自己也在用的免费配音工具 第一款&#xff0c;悦音配音工具 拥有强悍的AI智能配音技术&#xff0c;更专业&#xff0c;完美贴近真人配…

算法升级之路(六)

给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 示例 2: 输入: numRows 1 输出: [[1]] 解题思路&…

Spring IOC - ConfigurationClassPostProcessor源码解析

上文提到Spring在Bean扫描过程中&#xff0c;会手动将5个Processor类注册到beanDefinitionMap中&#xff0c;其中ConfigurationClassPostProcessor就是本文将要讲解的内容&#xff0c;该类会在refresh()方法中通过调用invokeBeanFactoryPosstProcessors(beanFactory)被调用。 5…

php收发邮件的多种方法?

1、添加扩展&#xff1a; # 第一种&#xff1a; composer require php-imap/php-imap # 第二种&#xff1a; composer require phpmailer/phpmailer2、这里采用第二种方式&#xff1a; <?php declare(strict_types1);namespace App\Controller\v1\email;use App\Controll…

【Android知识笔记】换肤专题

换肤其实也属于插件化专题的一个子话题,之所以单独拿出来,是因为它的处理方式比较特殊,相比插件化而言较简单一些。 系统内置的换肤功能支持 - Theme Android 系统中如果想修改应用的背景色,最简单的就是利用以下Theme相关的属性: 使用这些内置的属性可以实现一定程度上…

计算机视觉的相机选型

#你一般什么时候会用到GPT?# 目前市面上的工业相机大多是基于CCD&#xff08;ChargeCoupled Device&#xff09;或CMOS&#xff08;Complementary Metal Oxide Semiconductor&#xff09;芯片的相机。一般CCD制造工艺更加复杂&#xff0c;也会更贵一点&#xff01; 1、CCD工…

django如何连接sqlite数据库?

目录 一、SQLite数据库简介 二、Django连接SQLite数据库 1、配置数据库 2、创建数据库表 三、使用Django ORM操作SQLite数据库 1、定义模型 2、创建对象 3、查询对象 总结 本文将深入探讨如何在Django框架中连接和使用SQLite数据库。我们将介绍SQLite数据库的特点&…

k8spod

pod基本概念 (几种容器) pod 是k8s最小的创建和运行单元 一个pod包含几个容器&#xff0c;1个根容器/父容器/基础容器&#xff0c;一个或者多个应用容器/业务容器&#xff0c;pause容器 pod里面容器共享 network UTS IPC命令空间 k8s 创建的Pod 分为两种&#xff1a; 自主…

Android NDK开发详解之ndk-gdb

Android NDK开发详解之ndk-gdb 要求用法选项 线程支持 NDK 包含一个名为 ndk-gdb 的 Shell 脚本&#xff0c;可以启动命令行原生调试会话。偏好使用 GUI 的用户则应阅读在 Android Studio 中调试这篇文档。 要求 要运行命令行原生调试&#xff0c;必须满足以下要求&#xff1…

C#中LINQtoSQL的设置与连接

目录 一、首次安装LinqToSql类 二、非首次安装LinqToSql类 1.接受原有数据库连接 2.建立新的数据库连接 3.建立本地数据库连接 LINQ&#xff08;Language-Integrated Query&#xff0c;语言集成查询&#xff09;是微软公司提供的一项新技术&#xff0c;它能够将查询功能直…