一 前言
上一篇介绍了通过AF-PACKET的V1 版本进行网络包的捕获,比较新的Linux内核是支持V3版本的,相对于前两个版本(V2和V1比较相似,V2版本的时间精度从微秒提升到纳秒。)V3版本,具有以下的提升:
CPU使用率降低约15-20%
数据包捕获率提高约20%
数据包的密度提升2倍(不知道什么意思, 如 ~2x increase in packet density)
端口聚合分析
非静态数据帧大小,可以保存整个数据包。
所以这次就学习V3版本的用法,和其他能提示AF-PACKET抓包性能的均衡策略和方法。
二 V3版本的实战
V3的版本结构每次遍历和以前的不同是按照block遍历,当然下一层再按照frame遍历。
V3的时间戳精确度到纳秒。
struct tpacket_req3 {
unsigned int tp_block_size; // 每个连续内存块的最小尺寸(必须是 PAGE_SIZE * 2^n )
unsigned int tp_block_nr; // 内存块数量
unsigned int tp_frame_size; // 每个帧的大小(虽然V3中的帧长是可变的,但创建时还是会传入一个最大的允许值)
unsigned int tp_frame_nr; // 帧的总个数(必须等于 每个内存块中的帧数量*内存块数量)
unsigned int tp_retire_blk_tov; // 内存块的寿命(ms),超时后即使内存块没有被数据填入也会被内核停用,0意味着不设超时
unsigned int tp_sizeof_priv; // 每个内存块中私有空间大小,0意味着不设私有空间
unsigned int tp_feature_req_word;// 标志位集合(目前就支持1个标志 TP_FT_REQ_FILL_RXHASH)
}
// TPACKET_V3环形缓冲区每个帧的头部结构
struct tpacket3_hdr {
__u32 tp_next_offset; // 指向同一个内存块中的下一个帧
__u32 tp_sec; // 时间戳(s)
__u32 tp_nsec; // 时间戳(ns)
__u32 tp_snaplen; // 捕获到的帧实际长度
__u32 tp_len; // 帧的理论长度
__u32 tp_status; // 帧的状态
__u16 tp_mac; // 以太网MAC字段距离帧头的偏移量
__u16 tp_net;
union {
struct tpacket_hdr_variant1 hv1; // 包含vlan信息的子结构
};
__u8 tp_padding[8];
}
下面是内核文档中的收包例子,代码如下:
/* Written from scratch, but kernel-to-user space API usage
* dissected from lolpcap:
* Copyright 2011, Chetan Loke <loke.chetan@gmail.com>
* License: GPL, version 2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <poll.h>
#include <unistd.h>
#include <signal.h>
#include <inttypes.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#ifndef likely
# define likely(x) __builtin_expect(!!(x), 1)
#endif
#ifndef unlikely
# define unlikely(x) __builtin_expect(!!(x), 0)
#endif
struct block_desc {
uint32_t version;
uint32_t offset_to_priv;
struct tpacket_hdr_v1 h1;
};
struct ring {
struct iovec *rd;
uint8_t *map;
struct tpacket_req3 req;
};
static unsigned long packets_total = 0, bytes_total = 0;
static sig_atomic_t sigint = 0;
static void sighandler(int num)
{
sigint = 1;
}
static int setup_socket(struct ring *ring, char *netdev)
{
int err, i, fd, v = TPACKET_V3;
struct sockaddr_ll ll;
unsigned int blocksiz = 1 << 22, framesiz = 1 <&l