以太网帧结构

+-------------------+---------------------+---------------------+
|  目的MAC地址(6字节) |  源MAC地址(6字节)        | 以太网类型/长度(2字节) |
+-------------------+---------------------+---------------------+
|                       数据负载(46-1500字节)                    |
+-------------------+---------------------+---------------------+
|                           帧校验序列(FCS,4字节)               |
+---------------------------------------------------------------+

在 DPDK 中,以太网头部由 rte_ether_hdr 结构表示。以下是 rte_ether_hdr 结构的定义:

struct rte_ether_hdr {
    struct rte_ether_addr d_addr; /**< Destination address. */
    struct rte_ether_addr s_addr; /**< Source address. */
    uint16_t ether_type;          /**< Frame type. */
};

rte_ether_addr 结构表示一个 MAC 地址:

struct rte_ether_addr {
    uint8_t addr_bytes[6]; /**< Addr bytes in tx order */
};
代码示例:根据 MAC 地址转发数据包

常见的 ether_type 值

  • IPv4 (Internet Protocol version 4)
    ether_type 值:0x0800
    描述:表示以太网帧的负载是一个 IPv4 数据包。
  • ARP (Address Resolution Protocol)
    ether_type 值:0x0806
    描述:表示以太网帧的负载是一个 ARP 数据包。
  • Wake-on-LAN
    ether_type 值:0x0842
    描述:用于 Wake-on-LAN 魔术包。
  • IETF TRILL Protocol
    ether_type 值:0x22F3
    描述:表示以太网帧的负载是 TRILL 数据包。
  • DECnet Phase IV
    ether_type 值:0x6003
    描述:表示以太网帧的负载是 DECnet Phase IV 数据包。
  • RARP (Reverse Address Resolution Protocol)
    ether_type 值:0x8035
    描述:表示以太网帧的负载是一个 RARP 数据包。
  • AppleTalk (Ethertalk)
    ether_type 值:0x809B
    描述:表示以太网帧的负载是 AppleTalk 数据包。
  • AppleTalk ARP (AARP)
    ether_type 值:0x80F3
    描述:表示以太网帧的负载是 AppleTalk ARP 数据包。
  • VLAN-tagged frame (IEEE 802.1Q) & Shortest Path Bridging IEEE 802.1aq
    ether_type 值:0x8100
    描述:表示以太网帧包含 VLAN 标记。
  • IPX (Internetwork Packet Exchange)
    ether_type 值:0x8137
    描述:表示以太网帧的负载是 IPX 数据包。
  • QNX Qnet
    ether_type 值:0x8204
    描述:表示以太网帧的负载是 QNX Qnet 数据包。
  • IPv6 (Internet Protocol version 6)
    ether_type 值:0x86DD
    描述:表示以太网帧的负载是一个 IPv6 数据包。
  • Ethernet loopback packet
    ether_type 值:0x9000
    描述:用于以太网回环测试。
  • MPLS unicast
    ether_type 值:0x8847
    描述:表示以太网帧的负载是 MPLS 单播数据包。
  • MPLS multicast
    ether_type 值:0x8848
    描述:表示以太网帧的负载是 MPLS 多播数据包。
  • PPP over Ethernet (PPPoE) Discovery Stage
    ether_type 值:0x8863
    描述:表示以太网帧的负载是 PPPoE 发现阶段的数据包。
  • PPP over Ethernet (PPPoE) Session Stage
    ether_type 值:0x8864
    描述:表示以太网帧的负载是 PPPoE 会话阶段的数据包。
  • Jumbo Frames
    ether_type 值:0x8870
    描述:用于支持巨型帧。
  • EAP over LAN (IEEE 802.1X)
    ether_type 值:0x888E
    描述:表示以太网帧的负载是 EAPoL 数据包。
  • PROFINET
    ether_type 值:0x8892
    描述:表示以太网帧的负载是 PROFINET 实时数据包。

#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_mbuf.h>
#include <rte_lcore.h>
#include <rte_ether.h>
#include <stdio.h>

#define MAX_PKT_BURST 32
#define MEMPOOL_CACHE_SIZE 256

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

// 初始化端口函数
static int port_init(uint16_t port, struct rte_mempool *mbuf_pool) {
    struct rte_eth_conf port_conf = port_conf_default;
    const uint16_t rx_rings = 1, tx_rings = 1;
    uint16_t nb_rxd = 1024;
    uint16_t nb_txd = 1024;
    int retval;
    uint16_t q;

    // 检查端口是否有效
    if (port >= rte_eth_dev_count_avail())
        return -1;

    // 配置端口
    retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf);
    if (retval != 0)
        return retval;

    // 分配和设置 RX 队列
    for (q = 0; q < rx_rings; q++) {
        retval = rte_eth_rx_queue_setup(port, q, nb_rxd,
                                        rte_eth_dev_socket_id(port), NULL, mbuf_pool);
        if (retval < 0)
            return retval;
    }

    // 分配和设置 TX 队列
    for (q = 0; q < tx_rings; q++) {
        retval = rte_eth_tx_queue_setup(port, q, nb_txd,
                                        rte_eth_dev_socket_id(port), NULL);
        if (retval < 0)
            return retval;
    }

    // 启动端口
    retval = rte_eth_dev_start(port);
    if (retval < 0)
        return retval;

    // 启用混杂模式(可选)
    rte_eth_promiscuous_enable(port);

    return 0;
}

// 主循环函数
static void l2fwd_main_loop(void) {
    uint16_t port;
    struct rte_mbuf *bufs[MAX_PKT_BURST];
    unsigned lcore_id = rte_lcore_id();

    // 检查每个端口的 NUMA 节点
    for (port = 0; port < rte_eth_dev_count_avail(); port++) {
        if (rte_eth_dev_socket_id(port) != (int)rte_socket_id()) {
            printf("Warning: port %u is on remote NUMA node to polling thread.\n"
                   "Performance will not be optimal.\n", port);
        }
    }

    // 无限循环处理数据包
    while (1) {
        // 遍历所有端口
        for (port = 0; port < rte_eth_dev_count_avail(); port++) {
            // 从接收队列中读取数据包
            const uint16_t nb_rx = rte_eth_rx_burst(port, 0, bufs, MAX_PKT_BURST);
            if (nb_rx == 0)
                continue;

            // 遍历接收到的数据包
            for (uint16_t i = 0; i < nb_rx; i++) {
                struct rte_mbuf *m = bufs[i];
                struct rte_ether_hdr *eth_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *);

                // 根据目的 MAC 地址决定转发端口
                uint16_t dst_port = (eth_hdr->d_addr.addr_bytes[5] % rte_eth_dev_count_avail());

                // 发送数据包
                const uint16_t nb_tx = rte_eth_tx_burst(dst_port, 0, &m, 1);

                // 释放未能发送的数据包
                if (nb_tx < 1) {
                    rte_pktmbuf_free(m);
                }
            }
        }
    }
}

int main(int argc, char **argv) {
    struct rte_mempool *mbuf_pool;
    unsigned nb_ports;
    uint16_t portid;

    // 初始化 DPDK 环境
    int ret = rte_eal_init(argc, argv);
    if (ret < 0)
        rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");
    argc -= ret;
    argv += ret;

    // 检查可用端口数量
    nb_ports = rte_eth_dev_count_avail();
    if (nb_ports < 2)
        rte_exit(EXIT_FAILURE, "Error: number of ports must be >= 2\n");

    // 创建内存池
    mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", 8192 * nb_ports,
                                        MEMPOOL_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
    if (mbuf_pool == NULL)
        rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n");

    // 初始化所有端口
    RTE_ETH_FOREACH_DEV(portid) {
        if (port_init(portid, mbuf_pool) != 0)
            rte_exit(EXIT_FAILURE, "Cannot init port %" PRIu16 "\n", portid);
    }

    // 启动主循环
    l2fwd_main_loop();

    return 0;
}

备注:
假设有一个目的 MAC 地址 00:1A:2B:3C:4D:5E,其字节数组表示如下:

addr_bytes = [0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E]

在这个例子中,addr_bytes[5] 的值是 0x5E(十进制为94)。如果有4个可用端口,则计算如下:

uint16_t dst_port = (0x5E % 4); // 94 % 4 = 2


putao
5 声望0 粉丝

推动世界向前发展,改善民生。