环境
Ubuntu20.04.6
Visual Studio Code
VMware17
UDP/IP数据包
Ethernet 2
:以太网头Destination:目的MAC地址
Source:源MAC地址
Type:上层协议
0x0800:IP协议
0x0806:ARP协议
0x86DD:IPv6协议
Internet Protocol Version 4
:ipv4头Version:版本号,用来表明IP协议实现的版本号,当前一般为IPv4,即0100。
Header Length:首部长度,因为头部长度不固定(Option可选部分不固定),所以需要标识该分组的头部长度多少,用4bit表示,以4byte为单位,取值范围:5-15,即20(5_4)-60(15_4)byte(其他字段也是类似的计算方式,因为bit位是不够表示该字段的值)
Differentiated Services Field:服务类型,前3比特为优先权子字段(Precedence,现已被忽略)。第8比特保留未用。第4至第7比特分别代表延迟、吞吐量、可靠性和花费。当它们取值为1时分别代表要求最小时延、最大吞吐量、最高可靠性和最小费用。这4比特的服务类型中只能置其中1比特为1。可以全为0,若全为0则表示一般服务。服务类型字段声明了数据报被网络系统传输时可以被怎样处理。例如:TELNET协议可能要求有最小的延迟,FTP协议(数据)可能要求有最大吞吐量,SNMP协议可能要求有最高可靠性,NNTP(Network News Transfer Protocol,网络新闻传输协议)可能要求最小费用,而ICMP协议可能无特殊要求(4比特全为0)。实际上,大部分主机会忽略这个字段,但一些动态路由协议如OSPF(Open Shortest Path First Protocol)、IS-IS(Intermediate System to Intermediate System Protocol)可以根据这些字段的值进行路由决策。RFC2474的ToS取消了IP precedence字段而使用了DSCP,QoS里有描述,给QoS用来打标签。
Total Length:总长度,指明整个数据报的长度(以字节为单位,含头长度)。最大长度为65535字节。可用总长度减去头部长度获得实际报文数据的长度,取值范围0-65535byte,链路只允许1500byte,所以一般都需要MTU分片 。
Identification:标识,来唯一地标识主机发送的每一份数据报。通常每发一份报文,它的值会加1。通常与标记字段和分片偏移字段一起用于IP报文的分片。当原始报文大小超过MTU,那么就必须将原始数据进行分片。每个被分片的报文大小不得超过MTU,而这个字段还将在同一原始文件被分片的报文上打上相同的标记,以便接收设备可以识别出属于同一个报文的分片,“类似于进程号”,有时候电信会用他来识别流量是否是同一台主机(因为做了PAT后源ip都是一样的,鸡贼!)
Flags:标志,第1位没有被使用;第2位D是不分片位(DF),Do not fragment,顾名思义,不要分片,当DF位设置为1时,表示路由器不能对报文进行分片处理;- 第3位M表示还有后继分片(MF),More fragment,多分片,当路由器对报分进行分片时,除了最后一个分片的MF位设置为0外,其他所有分片的MF位均设置1,以便接收者直到收到MF位为0的分片为止;
Fragment Offset:片偏移,如果一份数据报要求分段的话,此字段指明该段偏移距原始数据报开始的位置。
Time to Live:生存时间,用来设置数据报最多可以经过的路由器数。由发送数据的源主机设置,通常为32、64(win7)、128、255(linux)等。每经过一个路由器,其值减1,直到0时该数据报被丢弃。
Protocol:协议,指明IP层所封装的上层协议类型,如ICMP(1)、IGMP(2) 、TCP(6)、UDP(17)等。
Header checksum status:首部校验和,内容是根据IP头部计算得到的校验和码。计算方法是:对头部中每个16比特进行二进制反码求和。(和ICMP、IGMP、TCP、UDP不同,IP不对头部后的数据进行校验)。
Source Address | Destination Address:用来标明发送IP数据报文的源主机地址和接收IP报文的目标主机地址。
User Datagram Protocol
:udp头Source Port:源端口号
Destination Port:目的端口号
Length:udp数据包总长度
Checksum:校验
UDP payload:数据负载
数据头定义
// myheader.h
#ifndef __MY_HEADER_H__
#define __MY_HEADER_H__
#define LN_HWADDR_LEN 6
typedef struct __ln_ether_hdr {
char src_hwaddr[LN_HWADDR_LEN];
char dst_hwaddr[LN_HWADDR_LEN];
short type;
} __attribute__((packed))ln_ether_hdr;
typedef struct __ln_ipv4_hdr {
char version_hdrlen;
char service_type;
short total_len;
short packet_id;
short packet_slice;
char time_to_live;
char next_proto;
short sum_check;
unsigned int src_ip;
unsigned int dst_ip;
} __attribute__((packed))ln_ipv4_hdr;
typedef struct __ln_udp_hdr {
short src_port;
short dst_port;
short total_len;
short sum_check;
}
#endif
代码
宏定义
#define NUM_MBUFS (4096-1) // 内存池元素数目
#define BURST_SIZE 32 // 接收数组大小
主要函数
struct rte_mempool *
rte_pktmbuf_pool_create(const char *name, unsigned int n,
unsigned int cache_size, uint16_t priv_size, uint16_t data_room_size,
int socket_id)
name:内存池的名称。
n:内存池中要分配的元素数目(对象个数)。
cache_size:预先分配到每个slab上的对象数量,提高访问效率。
priv_size:私有数据区域大小。
data_room_size:数据区域大小。
socket_id:套接字标识,指定内存分配在哪个NUMA节点。
static inline uint16_t
rte_eth_rx_burst(uint16_t port_id, uint16_t queue_id,
struct rte_mbuf **rx_pkts, const uint16_t nb_pkts)
port_id
:指定要从哪个以太网端口接收数据包。queue_id
:指定要从哪个队列接收数据包。rx_pkts
:一个指向存储接收到的数据包的内存缓冲区数组的指针。nb_pkts
:表示缓冲区数组大小,即最大能够存储多少个数据包。
int
rte_eth_rx_queue_setup(uint16_t port_id, uint16_t rx_queue_id,
uint16_t nb_rx_desc, unsigned int socket_id,
const struct rte_eth_rxconf *rx_conf,
struct rte_mempool *mp)
port_id
:指定要配置的以太网端口。rx_queue_id
:指定要配置的接收队列ID。nb_rx_desc
:表示该队列使用的接收描述符数量。socket_id
:表示分配内存时使用的NUMA节点。rx_conf
:一个指向接收配置结构体(rte_eth_rxconf)的指针,用于配置接收队列参数。可以为空指针,表示使用默认配置。mb_pool
:一个指向内存池结构体(rte_mempool)的指针,用于存储从硬件接口上接收到的数据包。
整体流程
少量代码的时候可以放上来,了解一下流程。
/*
* 版权所有: Copyright (c) 2024-2025 XXX Company. All rights reserved.
* 系统名称: Ubuntu20.04.6
* 文件名称: recv.c
* 内容摘要: 实现单线程的UDP接收
* 作 者: 黄彦杰 Lenn
* 设计日期: 2024-07-17
*/
#include <rte_ethdev.h>
#include <rte_eal.h>
#include <stdio.h>
#include <arpa/inet.h>
#define NUM_MBUFS (4096-1)
#define BURST_SIZE 32
static const struct rte_eth_conf port_dev_conf_default = {
.rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN}
};
int nDevPortId = 0;
/*
* 函数名称: ln_init_port
* 作 者: 黄彦杰 Lenn
* 设计日期: 2024-07-17
* 功能描述: 初始化虚拟网口,配置收发队列
* 返 回 值: None
*/
static void ln_init_port(struct rte_mempool* mbuf_pool) {
int nb_sys_port = rte_eth_dev_count_avail();
if (nb_sys_port == 0) {
rte_exit(EXIT_FAILURE, "No available system port");
}
struct rte_eth_dev_info dev_info;
rte_eth_dev_info_get(nDevPortId, &dev_info);
int nb_rx_queues = 1;
int nb_tx_queues = 0;
struct rte_eth_conf port_conf = port_dev_conf_default;
rte_eth_dev_configure(nDevPortId, nb_rx_queues, nb_tx_queues, &port_conf);
if (rte_eth_rx_queue_setup(nDevPortId, 0, 128, rte_eth_dev_socket_id(nDevPortId), NULL, mbuf_pool) < 0) {
rte_exit(EXIT_FAILURE, "Setup rx queue failed");
}
if (rte_eth_dev_start(nDevPortId) < 0) {
rte_exit(EXIT_FAILURE, "Could not start eth dev");
}
}
/*
* 函数名称: main
* 作 者: 黄彦杰 Lenn
* 设计日期: 2024-07-17
* 功能描述: while(1)主循环,收发
* 返 回 值: 0
*/
int main(int argc, char* argv[]) {
rte_eal_init(argc, argv);
struct rte_mempool* mbuf_pool = rte_pktmbuf_pool_create("mbuf pool", NUM_MBUFS, 0, 0,NUM_MBUFS, rte_socket_id());
if(mbuf_pool == NULL) {
rte_exit(EXIT_FAILURE, "Membuf pool create failed");
}
ln_init_port(mbuf_pool);
while (1) {
struct rte_mbuf* mbufs[BURST_SIZE];
int nb_recv = rte_eth_rx_burst(nDevPortId, 0, mbufs, BURST_SIZE);
unsigned i = 0;
for (i = 0; i < nb_recv; i++) {
struct rte_ether_hdr* ehdr = rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr*);
if (ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
continue;
}
struct rte_ipv4_hdr* iphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr*, sizeof(struct rte_ether_hdr));
if(iphdr->next_proto_id == IPPROTO_UDP) {
struct rte_udp_hdr* udphdr = (struct rte_udp_hdr*)(iphdr + 1);
int length = ntohs(udphdr->dgram_len);
*((char*)udphdr + length) = '\0';
struct in_addr addr;
addr.s_addr = iphdr->src_addr;
printf("---> udp pkt src %s:%d, ", inet_ntoa(addr), ntohs(udphdr->src_port));
addr.s_addr = iphdr->dst_addr;
printf("dst %s:%d %s\n", inet_ntoa(addr), ntohs(udphdr->dst_port), (char*)(udphdr + 1));
rte_pktmbuf_free(mbufs[i]);
}
}
}
return 0;
}
启动服务
- 进入编译目录配置环境变量
- 接管网卡
ifconfig eth0 down
./usertool/dpdk-setup.sh
# 执行插入和绑定操作
编译
Makefile
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2014 Intel Corporation
# binary name
APP = recv
# all source are stored in SRCS-y
SRCS-y := recv.c
# Build using pkg-config variables if possible
ifeq ($(shell pkg-config --exists libdpdk && echo 0),0)
all: shared
.PHONY: shared static
shared: build/$(APP)-shared
ln -sf $(APP)-shared build/$(APP)
static: build/$(APP)-static
ln -sf $(APP)-static build/$(APP)
PKGCONF=pkg-config --define-prefix
PC_FILE := $(shell $(PKGCONF) --path libdpdk)
CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk)
CFLAGS += -DALLOW_EXPERIMENTAL_API
LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk)
LDFLAGS_STATIC = -Wl,-Bstatic $(shell $(PKGCONF) --static --libs libdpdk)
build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
$(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)
build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
$(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)
build:
@mkdir -p $@
.PHONY: clean
clean:
rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
test -d build && rmdir -p build || true
else # Build using legacy build system
ifeq ($(RTE_SDK),)
$(error "Please define RTE_SDK environment variable")
endif
# Default target, detect a build directory, by looking for a path with a .config
RTE_TARGET ?= $(notdir $(abspath $(dir $(firstword $(wildcard $(RTE_SDK)/*/.config)))))
include $(RTE_SDK)/mk/rte.vars.mk
ifneq ($(CONFIG_RTE_EXEC_ENV_LINUX),y)
$(error This application can only operate in a linux environment, \
please change the definition of the RTE_TARGET environment variable)
endif
CFLAGS += -O3
CFLAGS += -DALLOW_EXPERIMENTAL_API
CFLAGS += $(WERROR_FLAGS)
include $(RTE_SDK)/mk/rte.extapp.mk
endif
运行
- 要向虚拟机
192.168.1.165
发送消息。
问题bug
你向虚拟机发送数据,但是你会发现这玩意发不过去,没想到吧嘿嘿嘿。这里有个坑。
netsh i i show in
netsh -c i i add neighbors 4 192.168.1.165 00-0c-29-05-6b-82
评论