dpdk的第一份代码

一些工具

工具 用途 版本
Ubuntu虚拟机 运行dpdk代码 16 Server
vmware 虚拟机软件 17
Source Insight 阅读代码、编写代码
NetAssist 模拟发包 5.0.14
xshell 远程连接 8(free for home/school)
wireshark 抓包,看包到底发出去了没有 4.2.4
Samba 在虚拟机里装,共享文件

代码

// 存放位置:share/dpdk-stable-19.08.2/examples/recv_send/recv.c

#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_mbuf.h>
#include <arpa/inet.h>

#include <stdio.h>

// 内存池大小,4096-1的含义是超过了4K就放别处
#define NUM_MBUFS (4096-1)

#define BURST_SIZE 32

int gDpdkPortId = 0;

static const struct rte_eth_conf port_conf_default = {
    .rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN }
};

// 设置网口接收数据,关联网口和dpdk程序
static void ng_init_port(struct rte_mempool *mbuf_pool) {

    // 1. 检测端口是否可用 (此处要运行dpdk的setup.sh,绑定端口)
    uint16_t nb_sys_ports= rte_eth_dev_count_avail(); //
    if (nb_sys_ports == 0) {
        rte_exit(EXIT_FAILURE, "没找到支持的eth端口\n");
    }

    // 获取 eth0 原生信息,方便后面用
    struct rte_eth_dev_info dev_info;
    rte_eth_dev_info_get(gDpdkPortId, &dev_info); 

    const int num_rx_queues = 1;//最大可用8,在此用1
    const int num_tx_queues = 0;//最大可用8,在此不用,也就是没有发送数据包的中断。因为本文件是recv,只接收不发送
    struct rte_eth_conf port_conf = port_conf_default;
    rte_eth_dev_configure(gDpdkPortId, num_rx_queues, num_tx_queues, &port_conf);


    if (rte_eth_rx_queue_setup(gDpdkPortId, 0 , 128, //0号接收队列共能囤积128条数据
        rte_eth_dev_socket_id(gDpdkPortId),NULL, mbuf_pool) < 0) {

        rte_exit(EXIT_FAILURE, "不能启动RX队列\n");

    }
    // 启动
    if (rte_eth_dev_start(gDpdkPortId) < 0 ) {
        rte_exit(EXIT_FAILURE, "无法启动\n");
    }

    rte_eth_promiscuous_enable(gDpdkPortId);

}


int main(int argc, char *argv[]) {

    // 初始化,主要是检查hugepage的设置,内存、cpu的分配等。就是在/usertools/dpdk-setup.sh中的一通设置
    if (rte_eal_init(argc, argv) < 0) {
        rte_exit(EXIT_FAILURE, "Error with EAL init\n");

    }

    // 内存池
    struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("mbuf pool", NUM_MBUFS,
        0, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
    if (mbuf_pool == NULL) {
        rte_exit(EXIT_FAILURE, "无法创建内存池\n");
    }

    ng_init_port(mbuf_pool);

    while (1) {

        struct rte_mbuf *mbufs[BURST_SIZE]; //数组指针, mbufs尚未分配实际空间,空间从内存池来
        unsigned num_recvd = rte_eth_rx_burst(gDpdkPortId, 0, mbufs, BURST_SIZE);// 接收的是以太网数据帧
                                    // 参数1:端口<注意此端口不是tcp/udp的端口号,而是网络适配器eth0的端口> 
                                    //参数2:从哪个队列接收
                                    //参数3:接收后放到哪
        if (num_recvd > BURST_SIZE) {
            rte_exit(EXIT_FAILURE, "接收出错,溢出\n");
        }

        unsigned i = 0;
        for (i = 0;i < num_recvd;i ++) {

            struct rte_ether_hdr *ehdr = rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr*);// rte_pktmbuf_mtod是宏定义。参数1:内存地址 参数2:转换成什么类型
                    //// ehdr就是拿出的以太网数据

            // 此处可看到对接口处理函数封装的已经很完善了
            if (ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
                // 如果不是ip协议,跳过
                continue;
            }

            // 如果是ip协议,取出
            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);

                uint16_t length = ntohs(udphdr->dgram_len); //经验:网络字节序2字节以上必须转,不管实际是什么序
                *((char*)udphdr + length) = '\0';

                struct in_addr addr;
                addr.s_addr = iphdr->src_addr;
                printf("src: %s:%d, ", inet_ntoa(addr), udphdr->src_port);

                addr.s_addr = iphdr->dst_addr;
                printf("dst: %s:%d, %s  length=%d\n", inet_ntoa(addr), udphdr->dst_port, 
                    (char *)(udphdr+1), length);

                rte_pktmbuf_free(mbufs[i]);
            }

        }

    }

}

接收成功需保证以下几个事情

  1. EAL初始化成功(不然直接报错)
  2. 代码中,使用rte_eth_promiscuous_enable() 函数,用于启用网络端口的混杂模式。
  3. NetAssist中,本地主机地址(就是发送地址)选那个跟eth0ip地址在一个网段中的。比如我的eth0信息为inet addr:192.168.3.20 Bcast:192.168.3.255 Mask:255.255.255.0,那么本地主机地址就选192.168.3.xxx的。如果选了其他的,可以用wireshark检测,包是发不出的,试过了。
  4. windows的apr表中,要准备好一条表项,是在192.168.3.xxx之下的,192.168.3.20 00-0c-29-14-56-cf 静态。添加命令:(cmd管理员)arp -s 192.168.3.20 00-0c-29-14-56-cf

总的大致流程是

(下载下来dpdk源码之后)

  1. /usertools/dpdk-setup.sh,运行39,等几分钟。

  2. 在/example下新建目录,叫send_recv,在里面编写代码

  3. 从/example其他目录下cp一个Makefile到/example/send_recv,改头两行,一个是目标的名字,一个是源文件名称

  4. 进行一些绑定:/usertools/dpdk-setup.sh,运行43~49,在46、47设置hugepage中设置参数,都写512即可
  5. 设置两个环境变量export RTE_SDK=/home/king/share/dpdk/dpdk-stable-19.08.2
    export RTE_TARGET=x86_64-native-linux-gcc
    。在/example/send_recv执行make,如果代码上有一些报错挨个解决
  6. 运行/example/send_recv/build/dpdk-recv.c,其中dpdk-recv.c是Makefile中给目标起的名字。
  7. 如果初始化成功了,就会进入不断接收数据包的阶段。此时先退出,检查windows的arp表后,用NetAssist发数据包测试。

结果

# ./build/dpdk_recv 
EAL: Detected 8 lcore(s)
EAL: Detected 1 NUMA nodes
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'PA'
EAL: Probing VFIO support...
EAL: VFIO support initialized
EAL: PCI device 0000:02:01.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 8086:100f net_e1000_em
EAL: PCI device 0000:02:06.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 8086:100f net_e1000_em
EAL: PCI device 0000:03:00.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 15ad:7b0 net_vmxnet3
EAL: PCI device 0000:0b:00.0 on NUMA socket -1
EAL:   Invalid NUMA socket, default to 0
EAL:   probe driver: 15ad:7b0 net_vmxnet3
src: 192.168.3.19:47138, dst: 192.168.3.20:47138, 你好,我的冰糖雪梨  length=35