当前位置: 首页 > news >正文

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协议
  • linux中断下文工作队列之延迟工作(中断六)
  • 第三届字节跳动奖学金官宣开奖,13位优秀科研学子每人获10万奖学金
  • 「永不失联」产品创新与升级系列发布,预约直播“即将发车”
  • 结构体内存分配
  • 基于STC系列单片机实现定时器0扫描数码管显示定时器/计数器1作为计数器1产生频率的功能
  • mysql存在10亿条数据,如何高效随机返回N条纪录,sql如何写
  • Android使用Glide类加载服务器中的图片
  • 2023 年 Github 万圣节彩蛋
  • matlabR2021a正版免费使用
  • ardupilot开发 --- 深度相机 篇
  • 高校教务系统登录页面JS分析——西安电子科技大学
  • Java中 #{}和${}的区别
  • 软考系统架构师案例分析知识点整理
  • JavaScript 如何正确处理 Unicode 编码问题!
  • angular2 简述
  • Angular6错误 Service: No provider for Renderer2
  • Apache Spark Streaming 使用实例
  • CentOS从零开始部署Nodejs项目
  • input的行数自动增减
  • Joomla 2.x, 3.x useful code cheatsheet
  • quasar-framework cnodejs社区
  • vue-cli在webpack的配置文件探究
  • webpack+react项目初体验——记录我的webpack环境配置
  • 基于webpack 的 vue 多页架构
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 【云吞铺子】性能抖动剖析(二)
  • 我们雇佣了一只大猴子...
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • ​secrets --- 生成管理密码的安全随机数​
  • #DBA杂记1
  • #HarmonyOS:Web组件的使用
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • (1)Nginx简介和安装教程
  • (9)目标检测_SSD的原理
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (附源码)ssm高校社团管理系统 毕业设计 234162
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (数据结构)顺序表的定义
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (原创)Stanford Machine Learning (by Andrew NG) --- (week 9) Anomaly DetectionRecommender Systems...
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • (轉)JSON.stringify 语法实例讲解
  • .NET CORE 2.0发布后没有 VIEWS视图页面文件
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .NET 中让 Task 支持带超时的异步等待
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args
  • .NET/C# 使用 #if 和 Conditional 特性来按条件编译代码的不同原理和适用场景
  • .net2005怎么读string形的xml,不是xml文件。
  • .net和jar包windows服务部署
  • .pyc文件是什么?