六、VPP数据包处理框架
VPP(Vector Packet Processing)是一个高性能的数据包处理框架,它是Cisco开源的一个项目。VPP提供了一种可扩展的、用户空间的数据包处理引擎,可以通过与多种硬件平台和操作系统集成来实现快速、可扩展的网络应用程序。
VPP支持多个协议栈(例如TCP/IP、UDP/IP等),并提供了一系列基本功能模块,如ARP缓存管理、IP地址管理、ACL过滤等。它还支持虚拟化和容器化部署,并具有灵活的API和可扩展的插件架构,可以轻松地定制以适应不同类型的网络应用程序需求。
VPP是一个高度可编程化和可定制化的数据包处理框架,在运营商级网络设备、云计算环境中得到广泛应用。除了Cisco之外,许多公司和组织也在使用VPP构建自己的网络产品或服务。
以下安装方式在centos7上安装测试(可用)
有三种安装方式:源码安装、yum安装、vpp-config安装
源码安装:
1.使用git将VPP源码克隆下来(没有git可使用 yum install git -y
安装)
[root@localhost ~]# mkdir source [root@localhost ~]# cd source [root@localhost source]# git clone https://gerrit.fd.io/r/vpp
2. 安装依赖环境,进入VPP目录下执行:
[root@localhost source]# cd vpp [root@localhost vpp]# yum install -y epel-release python-pip net-tools [root@localhost vpp]# make install-dep
3. 安装dpdk,执行第4步代码编译时,会自动下载dpdk并一起编译(可忽略)
[root@localhost vpp]# make dpdk-install-dev
4. 进行代码编译(make distclean
可以清除编译生成文件 )
[root@localhost vpp]# make build
5. 制作rpm包
[root@localhost vpp]# make pkg-rpm
6. 安装VPP
[root@localhost vpp]# cd build-root/ [root@localhost build-root]# rpm -i vpp*.rpm
7. 启动VPP(并设置开机启动)
[root@localhost ~]# systemctl enable vpp [root@localhost ~]# systemctl start vpp [root@localhost ~]# systemctl status vpp.service
8.测试安装是否成功
root@localhost ~]# vppctl 显示如下代表安装成功:
七、ClickOS高性能虚拟机
ClickOS是一种专门为云计算设计的高性能虚拟机,它基于Xen虚拟化技术,并集成了高效的网络协议栈和数据平面加速技术。ClickOS主要用于构建可定制、低延迟、高吞吐量的网络功能虚拟化(NFV)系统,如防火墙、负载均衡器、路由器等。ClickOS的特点在于其精简的操作系统内核,以及对热插拔设备和硬件辅助虚拟化技术的支持,使得它可以实现比传统虚拟机更高的性能和更低的资源开销。
八、http://FD.io数据平面开发工具包
http://FD.io是一个开放源代码的数据平面开发工具包,它提供了一组数据平面服务和库,以便于构建高性能、可扩展、灵活的网络功能虚拟化(NFV)系统。http://FD.io旨在为各种硬件和软件环境下的网络应用程序提供通用的、高效的数据平面支持,并且可以快速地配置和部署。http://FD.io项目由Linux基金会主导,采用Apache 2.0许可证发布,目前已成为全球最大的数据平面开源社区之一。
http://FD.io的一个关键项目是VPP(Vector Packet Processing:矢量报文处理)。VPP是高度模块化的项目,新开发的功能模块很容易被集成进VPP,而不影响VPP底层的代码框架。这就给了开发者很大的灵活性,可以创新不计其数的报文处理解决方案。
除了VPP,http://FD.io充分利用DPDK特性以支持额外的项目,包括NSH_SFC, Honeycomb 和ONE来加速网络功能虚拟化的数据面。此外,http://FD.io还与其他关键的开源项目进行集成,以支持网络功能虚拟化和软件定义网络。目前已经集成的开源项目包括:K8s、OpenStack、ONAP和OpenDaylight。
下图是http://FD.io的网络生态系统概览:
VPP简介
VPP到底是什么?一个软件路由器?一个虚拟交换机?一个虚拟网络功能?事实上,它包含所有这些,并且包含更多。VPP是一个模块化和可扩展的软件框架,用于创建网络数据面应用程序。更重要的是,VPP代码为现代通用处理器平台(x86、ARM、PowerPC等)而生,并把重点放在优化软件和硬件接口上,以便用于实时的网络输入输出操作和报文处理。
VPP充分利用通用处理器优化技术,包括矢量指令(例如Intel SSE, AVX)以及I/O和CPU缓存间的直接交互(例如 Intel DDIO),以达到最好的报文处理性能。利用这些优化技术的好处是:使用最少的CPU核心指令和时钟周期来处理每个报文。在最新的Intel Xeon-SP处理器上,可以达到Tbps的处理性能。
VPP架构
VPP是一个有效且灵活的数据面,它包括一系列按有向图组织的转发图形节点(graph node)和一个软件框架。该软件框架包含基本的数据结构、定时器、驱动程序、在图形节点间分配CPU时间片的调度器、性能调优工具,比如计数器和内建的报文跟踪功能。
VPP采用插件(plugin)架构,插件与直接内嵌于VPP框架中的模块一样被同等对待。原则上,插件是实现某一特定功能的转发图形节点,但也可以是一个驱动程序,或者另外的CLI。插件能被插入到VPP有向图的任意位置,从而有利于快速灵活地开发新功能。因此,插件架构使开发者能够充分利用现有模块快速开发出新功能。
VPP架构:报文处理有向图
输入节点轮询(或中断驱动)接口的接收队列,获取批量报文。接着把这些报文按照下个节点功能组成一个矢量(vector)或者一帧(frame)。比如:输入节点收集所有IPv4的报文并把它们传递给ip4-input节点;输入节点收集所有IPv6的报文并把它们传递给ip6-input节点。当ip6-input节点被调度时,它取出这一帧报文,利用双循环(dual-loop) 或四循环(quad-loop)以及预取报文到CPU缓存技术处理报文,以达到最优性能。这能够通过减少缓存未命中数来有效利用CPU缓存。当ip6-input节点处理完当前帧的所有报文后,把报文传递到后续不同的节点。比如:如果某报文校验失败,就被传送到error-drop节点;正常报文被传送到ip6-lookup节点。一帧报文依次通过不同的图形节点,直到它们被interface-output节点发送出去。
VPP图形节点的处理逻辑
按照网络功能一次处理一帧报文,有几个好处:
- 从软件工程的角度看,每一个图形节点是独立和自治的。
- 从性能的角度看,主要的好处是可以优化CPU指令缓存(i-cache)的使用。当前帧的第一个报文加载当前节点的指令到指令缓存,当前帧的后续报文就可以“免费”使用指令缓存。这里,VPP充分利用了CPU的超标量结构,使报文内存加载和报文处理交织进行,达到更有效地利用CPU处理流水线。
- VPP也充分利用了CPU的预测执行功能来达到更好的性能。从预测重用报文间的转发对象(比如邻接表和路由查找表),以及预先加载报文内容到CPU的本地数据缓存(d-cache)供下一次循环使用,这些有效使用计算硬件的技术,使得VPP可以利用更细粒度的并行性。
VPP有向图处理的特性,使它成为一个松耦合、高度一致的软件架构。每一个图形节点利用一帧报文作为输入和输出的最小处理单位,这就提供了松耦合的特性。通用功能被组合到每个图形节点中,这就提供了高度一致的架构。
在有向图中的节点是可替代的。当这个特性和VPP支持动态加载插件节点相结合时,新功能能被快速开发,而不需要新建和编译一个定制的代码版本。
九、Pktgen-DPDK数据包生成工具
Pktgen-DPDK是一种基于DPDK(Data Plane Development Kit)的数据包生成工具,可以用于测试和评估网络设备、协议栈等各种网络应用程序。它提供了丰富的功能,可以进行不同类型和大小的数据包生成,以及流量控制、速率控制等测试。此外,Pktgen-DPDK还支持多核处理、超线程和NUMA架构,并提供了Web界面来方便用户使用。由于采用了DPDK技术,Pktgen-DPDK具有非常高的性能和低延迟,并广泛应用于云计算、虚拟化、SDN/NFV等领域。
pktgen-dpdk使用dpdk加速包的发送接收,也可以发送接收pcap包,命令行如下:
./app/app/x86_64-native-linuxapp-gcc/pktgen -l 0-4 -n 3 -- -P -m "[11:3].0,[2:4].1" -s 0:[.pcap_filepath] (pktgen-dpdk.3.4.8)
pktgen-dpdk逻辑设计
在pktgen-main.c文件中包含了main主入口函数main()以及参数配置函数pktgen_parse_args(),其中pktgen结构体是所有的参数都是用的,参数配置函数主要是对pktgen结构中个成员赋值。
main函数中pktgen初始化如下:
memset(&pktgen, 0, sizeof(pktgen)); pktgen.flags = PRINT_LABELS_FLAG; pktgen.ident = 0x1234; pktgen.nb_rxd = DEFAULT_RX_DESC; pktgen.nb_txd = DEFAULT_TX_DESC; pktgen.nb_ports_per_page = DEFAULT_PORTS_PER_PAGE; if ( (pktgen.l2p = l2p_create()) == NULL) pktgen_log_panic("Unable to create l2p"); pktgen.portdesc_cnt = get_portdesc(pktgen.portlist, pktgen.portdesc, RTE_MAX_ETHPORTS, 0);
然后初始化log,cpu
pktgen_init_log(); pktgen_cpu_init();
配置初始化port信息
void pktgen_config_ports(void) { uint32_t lid, pid, i, s, q, sid; rxtx_t rt; pkt_seq_t *pkt; port_info_t *info; char buff[RTE_MEMZONE_NAMESIZE]; int32_t ret, cache_size; char output_buff[256] = { 0 }; uint64_t ticks; /* Find out the total number of ports in the system. */ /* We have already blacklisted the ones we needed to in main routine. */ pktgen.nb_ports = rte_eth_dev_count(); if (pktgen.nb_ports > RTE_MAX_ETHPORTS) pktgen.nb_ports = RTE_MAX_ETHPORTS; if (pktgen.nb_ports == 0) pktgen_log_panic("*** Did not find any ports to use ***"); pktgen.starting_port = 0; /* Setup the number of ports to display at a time */ if (pktgen.nb_ports > pktgen.nb_ports_per_page) pktgen.ending_port = pktgen.starting_port + pktgen.nb_ports_per_page; else pktgen.ending_port = pktgen.starting_port + pktgen.nb_ports; pg_port_matrix_dump(pktgen.l2p); .... .... for (s = 0; s < NUM_TOTAL_PKTS; s++) pktgen_port_defaults(pid, s);
其中主要获取cpu端口数,以及填充结构体info,其中函数pktgen_port)defaults()主要设置一些默认显示的信息,其中里面的函数pktgen_packet_rate()计算传输率,此函数作用与动态的展示发送接收的数据参数。
main()往下调用的函数是pktgen_clear_display():
void pktgen_clear_display(void) { if (!scrn_is_paused()) { scrn_pause(); scrn_cls(); scrn_pos(100, 1); pktgen_update_display(); scrn_resume(); pktgen_page_display(NULL, NULL); } }
其中pktgen_update_dsiplay()修改pktgen.flag标志:
void pktgen_page_display(struct rte_timer *tim __rte_unused, void *arg __rte_unused) { static unsigned int update_display = 1; /* Leave if the screen is paused */ if (scrn_is_paused()) return; scrn_save(); if (pktgen.flags & UPDATE_DISPLAY_FLAG) { pktgen.flags &= ~UPDATE_DISPLAY_FLAG; update_display = 1; } update_display--; if (update_display == 0) { update_display = UPDATE_DISPLAY_TICK_INTERVAL; _page_display(); if (pktgen.flags & PRINT_LABELS_FLAG) pktgen.flags &= ~PRINT_LABELS_FLAG; } scrn_restore(); pktgen_print_packet_dump(); }
函数中主要执行两个函数_page_display(),pktgen_printf_paket_dump(),前者调用pktgen_page_stats(),显示端口上的统计信息:
void pktgen_page_stats(void) { port_info_t *info; unsigned int pid, col, row; struct rte_eth_stats *rate, *cumm, *prev; unsigned sp; char buff[32]; int display_cnt; if (pktgen.flags & PRINT_LABELS_FLAG) pktgen_print_static_data(); ... ...
其中函数pktgen_print_static_data()显示一些静态数据,动态数据主要通过info结构体传输。
然后main函数调用rte_timer_setup()
void rte_timer_setup(void) { int lcore_id = rte_get_master_lcore(); /* init RTE timer library */ rte_timer_subsystem_init(); /* init timer structures */ rte_timer_init(&timer0); rte_timer_init(&timer1); /* load timer0, every 1/2 seconds, on Display lcore, reloaded automatically */ rte_timer_reset(&timer0, UPDATE_DISPLAY_TICK_RATE, PERIODICAL, lcore_id, pktgen_page_display, NULL); /* load timer1, every second, on timer lcore, reloaded automatically */ rte_timer_reset(&timer1, pktgen.hz, PERIODICAL, lcore_id, pktgen_process_stats, NULL); }
其中timer0,用于更新显示,timer1用于统计数据。
main函数接着调用pktgen_cli_start(),最终调用cli_start(),用于执行运行时参数。
pktgen 流量生成器
下面说一下发送接收包,在main函数的前部分还有一个重要的函数:
ret = rte_eal_remote_launch(pktgen_launch_one_lcore, NULL, i);
/* Configure and initialize the ports */ pktgen_config_ports(); pktgen_log_info(""); pktgen_log_info("=== Display processing on lcore %d", rte_lcore_id()); /* launch per-lcore init on every lcore except master and master + 1 lcores */ for (i = 0; i < RTE_MAX_LCORE; i++) { if ( (i == rte_get_master_lcore()) || !rte_lcore_is_enabled(i) ) continue; ret = rte_eal_remote_launch(pktgen_launch_one_lcore, NULL, i); if (ret != 0) pktgen_log_error("Failed to start lcore %d, return %d", i, ret); } rte_delay_ms(1000); /* Wait for the lcores to start up. */
其中pktgen_launch-one_lcore()调用pktgen_main_tx_loop(),pktgen_mian_rx_loop(),pktgen_main_rxtx_loop()。
这些函数相应的调用函数,同时更新pktgen结构或是其中的成员,以pktgen_main_rxtx_loop()为例。
pktgen_main_rxtx_loop() -> pktgen_main_transmit() -> pktgen_send_pkts() ->pktgen_send_burst() -> _send_burst_..()-> pkt_do_tx_tap() -> write()
下面说一下pktgen作为流量生成器的一些个人理解。
pktgen作为linux内核模块的一部分,因此可以直接加载内核模块generate流量(lsmod pktgen),网上参考资料说是产生udp类型的packet,在此详细探讨一下。
在pktgen_main_transmit()中:
static __inline__ void pktgen_main_transmit(port_info_t *info, uint16_t qid) { struct rte_mempool *mp = NULL; uint32_t flags; flags = rte_atomic32_read(&info->port_flags); /* * Transmit ARP/Ping packets if needed */ if ((flags & SEND_ARP_PING_REQUESTS)) pktgen_send_special(info, flags); /* When not transmitting on this port then continue. */ if (flags & SENDING_PACKETS) { mp = info->q[qid].tx_mp; if (flags & (SEND_RANGE_PKTS | SEND_PCAP_PKTS | SEND_SEQ_PKTS)) { if (flags & SEND_RANGE_PKTS) mp = info->q[qid].range_mp; else if (flags & SEND_SEQ_PKTS) mp = info->q[qid].seq_mp; else if (flags & SEND_PCAP_PKTS) mp = info->q[qid].pcap_mp; } if (rte_atomic32_read(&info->q[qid].flags) & CLEAR_FAST_ALLOC_FLAG) pktgen_setup_packets(info, mp, qid); pktgen_send_pkts(info, qid, mp); } flags = rte_atomic32_read(&info->q[qid].flags); if (flags & DO_TX_FLUSH) pktgen_tx_flush(info, qid); }
最终调用函数pktgen_setup_cb(),调用了pktgen_range_ctor()和pktgen_packet_ctor()
其中结构体:
typedef struct pkt_seq_s { /* Packet type and information */ struct ether_addr eth_dst_addr; /**< Destination Ethernet address */ struct ether_addr eth_src_addr; /**< Source Ethernet address */ struct cmdline_ipaddr ip_src_addr; /**< Source IPv4 address also used for IPv6 */ struct cmdline_ipaddr ip_dst_addr; /**< Destination IPv4 address */ uint32_t ip_mask; /**< IPv4 Netmask value */ uint16_t sport; /**< Source port value */ uint16_t dport; /**< Destination port value */ uint16_t ethType; /**< IPv4 or IPv6 */ uint16_t ipProto; /**< TCP or UDP or ICMP */ uint16_t vlanid; /**< VLAN ID value if used */ uint8_t cos; /**< 802.1p cos value if used */ uint8_t tos; /**< tos value if used */ uint16_t ether_hdr_size;/**< Size of Ethernet header in packet for VLAN ID */ uint32_t mpls_entry; /**< MPLS entry if used */ uint16_t qinq_outerid; /**< Outer VLAN ID if Q-in-Q */ uint16_t qinq_innerid; /**< Inner VLAN ID if Q-in-Q */ uint32_t gre_key; /**< GRE key if used */ uint16_t pktSize; /**< Size of packet in bytes not counting FCS */ uint16_t pad0; uint32_t gtpu_teid; /**< GTP-U TEID, if UDP dport=2152 */ uint8_t seq_enabled; /**< Enable or disable this sequence through GUI */ pkt_hdr_t hdr __rte_cache_aligned; /**< Packet header data */ uint8_t pad[MBUF_SIZE - sizeof(pkt_hdr_t)]; } pkt_seq_t __rte_cache_aligned;
void pktgen_packet_ctor(port_info_t *info, int32_t seq_idx, int32_t type){ ..... if (likely(pkt->ethType == ETHER_TYPE_IPv4)) { if (likely(pkt->ipProto == PG_IPPROTO_TCP)) { //构造TCP if (pkt->dport != PG_IPPROTO_L4_GTPU_PORT) { /* Construct the TCP header */ pktgen_tcp_hdr_ctor(pkt, l3_hdr, ETHER_TYPE_IPv4); /* IPv4 Header constructor */ pktgen_ipv4_ctor(pkt, l3_hdr); } else { /* Construct the GTP-U header */ pktgen_gtpu_hdr_ctor(pkt, l3_hdr, pkt->ipProto, GTPu_VERSION | GTPu_PT_FLAG, 0, 0, 0); /* Construct the TCP header */ pktgen_tcp_hdr_ctor(pkt, l3_hdr, ETHER_TYPE_IPv4); /* IPv4 Header constructor */ pktgen_ipv4_ctor(pkt, l3_hdr); } } else if (pkt->ipProto == PG_IPPROTO_UDP) { if (pkt->dport != PG_IPPROTO_L4_GTPU_PORT) { /* Construct the UDP header */ pktgen_udp_hdr_ctor(pkt, l3_hdr, ETHER_TYPE_IPv4); /* IPv4 Header constructor */ pktgen_ipv4_ctor(pkt, l3_hdr); } else { /* Construct the GTP-U header */ pktgen_gtpu_hdr_ctor(pkt, l3_hdr, pkt->ipProto, GTPu_VERSION | GTPu_PT_FLAG, 0, 0, 0); /* Construct the UDP header */ pktgen_udp_hdr_ctor(pkt, l3_hdr, ETHER_TYPE_IPv4); /* IPv4 Header constructor */ pktgen_ipv4_ctor(pkt, l3_hdr); } } else if (pkt->ipProto == PG_IPPROTO_ICMP) { udpip_t *uip; icmpv4Hdr_t *icmp; /* Start from Ethernet header */ uip = (udpip_t *)l3_hdr; /* Create the ICMP header */ uip->ip.src = htonl(pkt->ip_src_addr.addr.ipv4.s_addr); uip->ip.dst = htonl(pkt->ip_dst_addr.addr.ipv4.s_addr); tlen = pkt->pktSize - (pkt->ether_hdr_size + sizeof(ipHdr_t)); uip->ip.len = htons(tlen); uip->ip.proto = pkt->ipProto; icmp = (icmpv4Hdr_t *)&uip->udp; icmp->code = 0; if ( (type == -1) || (type == ICMP4_TIMESTAMP)) { icmp->type = ICMP4_TIMESTAMP; icmp->data.timestamp.ident = 0x1234; icmp->data.timestamp.seq = 0x5678; icmp->data.timestamp.originate = 0x80004321; icmp->data.timestamp.receive = 0; icmp->data.timestamp.transmit = 0; } else if (type == ICMP4_ECHO) { icmp->type = ICMP4_ECHO; icmp->data.echo.ident = 0x1234; icmp->data.echo.seq = 0x5678; icmp->data.echo.data = 0; } icmp->cksum = 0; /* ICMP4_TIMESTAMP_SIZE */ tlen = pkt->pktSize - (pkt->ether_hdr_size + sizeof(ipHdr_t)); icmp->cksum = cksum(icmp, tlen, 0); if (icmp->cksum == 0) icmp->cksum = 0xFFFF; /* IPv4 Header constructor */ pktgen_ipv4_ctor(pkt, l3_hdr); } } else if (pkt->ethType == ETHER_TYPE_IPv6) { if (pkt->ipProto == PG_IPPROTO_TCP) { /* Construct the TCP header */ pktgen_tcp_hdr_ctor(pkt, l3_hdr, ETHER_TYPE_IPv6); /* IPv6 Header constructor */ pktgen_ipv6_ctor(pkt, l3_hdr); } else if (pkt->ipProto == PG_IPPROTO_UDP) { /* Construct the UDP header */ pktgen_udp_hdr_ctor(pkt, l3_hdr, ETHER_TYPE_IPv6); /* IPv6 Header constructor */ pktgen_ipv6_ctor(pkt, l3_hdr); } } else if (pkt->ethType == ETHER_TYPE_ARP) { /* Start from Ethernet header */ arpPkt_t *arp = (arpPkt_t *)l3_hdr; arp->hrd = htons(1); arp->pro = htons(ETHER_TYPE_IPv4); arp->hln = ETHER_ADDR_LEN; arp->pln = 4; /* FIXME make request/reply operation selectable by user */ arp->op = htons(2); ether_addr_copy(&pkt->eth_src_addr, (struct ether_addr *)&arp->sha); arp->spa._32 = htonl(pkt->ip_src_addr.addr.ipv4.s_addr); ether_addr_copy(&pkt->eth_dst_addr, (struct ether_addr *)&arp->tha); arp->tpa._32 = htonl(pkt->ip_dst_addr.addr.ipv4.s_addr); }
这里主要是根据类型填充首部结构,其中pktgen_tcp_hdr_ctor()填充ip首部和tcp首部,return l3_hdr,然后在调用pktgen_ipv4_ctor()填充ip header对于pktgen_range_ctor(), 主要是根据range_info_t结构体重的最大最下值以及步长递增地修改首部结构。
从上面可以看书pktgen不只是产生udp类型的数据包,也可以产生其他类型的数据包,但是只是产生了流量,而没有payload。
pktgen-dpdk发送pcap包
发送pcap数据包需要使用 -s:[pcap.filepcath]参数。
static int pktgen_parse_args(int argc, char **argv) { .... case 's': /* Read a PCAP packet capture file (stream) */ port = strtol(optarg, NULL, 10); //将字符串port转换为长整型port p = strchr(optarg, ':'); // ++p指向待发送的pcap文件 if ( (p == NULL) || (pktgen.info[port].pcap = _pcap_open(++p, port)) == NULL) { pktgen_log_error( "Invalid PCAP filename (%s) must include port number as P:filename", optarg); pktgen_usage(prgname); return -1; ....... }
此处p指向pcap文件(此处为绝对路劲)
pcap_info_t * _pcap_open(char *filename, uint16_t port) { pcap_info_t *pcap = NULL; if (filename == NULL) { printf("%s: filename is NULL\n", __FUNCTION__); goto leave; } pcap = (pcap_info_t *)rte_malloc("PCAP info", sizeof(pcap_info_t), RTE_CACHE_LINE_SIZE); if (pcap == NULL) { printf("%s: malloc failed for pcap_info_t structure\n", __FUNCTION__); goto leave; } memset((char *)pcap, 0, sizeof(pcap_info_t)); pcap->fd = fopen((const char *)filename, "r"); if (pcap->fd == NULL) { printf("%s: failed for (%s)\n", __FUNCTION__, filename); goto leave; } if (fread(&pcap->info, 1, sizeof(pcap_hdr_t), pcap->fd) != sizeof(pcap_hdr_t) ) { printf("%s: failed to read the file header\n", __FUNCTION__); goto leave; } /* Default to little endian format. */ pcap->endian = LITTLE_ENDIAN; pcap->filename = strdup(filename); /* Make sure we have a valid PCAP file for Big or Little Endian formats. */ if ( (pcap->info.magic_number != PCAP_MAGIC_NUMBER) && (pcap->info.magic_number != ntohl(PCAP_MAGIC_NUMBER)) ) { printf("%s: Magic Number does not match!\n", __FUNCTION__); fflush(stdout); goto leave; } /* Convert from big-endian to little-endian. */ if (pcap->info.magic_number == ntohl(PCAP_MAGIC_NUMBER) ) { printf( "PCAP: Big Endian file format found, converting to little endian\n"); pcap->endian = BIG_ENDIAN; pcap->info.magic_number = ntohl(pcap->info.magic_number); pcap->info.network = ntohl(pcap->info.network); pcap->info.sigfigs = ntohl(pcap->info.sigfigs); pcap->info.snaplen = ntohl(pcap->info.snaplen); pcap->info.thiszone = ntohl(pcap->info.thiszone); pcap->info.version_major = ntohs(pcap->info.version_major); pcap->info.version_minor = ntohs(pcap->info.version_minor); } _pcap_info(pcap, port, 0); return pcap; leave: _pcap_close(pcap); fflush(stdout); return NULL; }
在 pkgen_config_ports()中
/* Setup the PCAP file for each port */ if (pktgen.info[pid].pcap != NULL) if (pktgen_pcap_parse(pktgen.info[pid].pcap, info, q) == -1) pktgen_log_panic("Cannot load PCAP file for port %d", pid); /* Find out the link speed to program the WTHRESH value correctly. */ pktgen_get_link_status(info, pid, 0);
其中调用了pktgen_pcap_parse(),在里面读取pcap文件数据的函数是_pcap_read()
size_t _pcap_read(pcap_info_t *pcap, pcaprec_hdr_t *pHdr, char *pktBuff, uint32_t bufLen) { do { if (fread(pHdr, 1, sizeof(pcaprec_hdr_t), pcap->fd) != sizeof(pcaprec_hdr_t) ) return 0; /* Convert the packet header to the correct format. */ _pcap_convert(pcap, pHdr); /* Skip packets larger then the buffer size. */ if (pHdr->incl_len > bufLen) { (void)fseek(pcap->fd, pHdr->incl_len, SEEK_CUR); return pHdr->incl_len; } return fread(pktBuff, 1, pHdr->incl_len, pcap->fd); } while (1); }
然后调用dpdk的rte_mempool_create(),可以看到每次只能发送一个pcap文件,如果需要发送多个pcap文件,则需要解析多个pcap文件路劲,赋值给pcap.fd(可以使用缓存预先存储)。