Program Listing for File eth_rxtx.h

Return to documentation for file (include/eth_rxtx.h)

#pragma once

#include <dpdk_wrappers.h>
#include <network_types.h>
#include <rte_cycles.h>
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_flow.h>
#include <rte_hexdump.h>
#include <rte_lcore.h>
#include <rte_mbuf.h>

#include <new>
#include <span>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "spdlog/spdlog.h"

#define CACHELINE_SIZE 64

struct alignas(CACHELINE_SIZE) Stats {
    size_t num_bad_l4_cksum;
    size_t num_bad_ip_cksum;
    size_t num_bad_tx_cksum;
    size_t num_total;
    size_t num_sent;
    size_t num_bytes_sent;
};

struct EthDevConf {
    uint16_t nb_tx_queues;
    uint16_t nb_rx_queues;

    uint16_t nb_tx_descrs;
    uint16_t nb_rx_descrs;

    bool enable_rss;
};

enum class L3Type {
    Ipv4,
    Ipv6
};

enum class L4Type {
    UDP,
    TCP
};

template <class opts>
class EthRxTx {
public:
    static tl::expected<EthRxTx, std::string> init(const EthDevConf &config,
        std::string_view device_name, rte_mempool *mempool);

    EthRxTx(const EthRxTx &) = delete;
    EthRxTx &operator=(const EthRxTx &) = delete;
    EthRxTx &operator=(EthRxTx &&other) = delete;

    // Move constructor is required for init function.
    EthRxTx(EthRxTx &&other)
        : config(other.config),
          portid(other.portid),
          addr(other.addr),
          stats(std::move(other.stats)),
          flow_configurations(std::move(other.flow_configurations)),
          _valid(other._valid) {
        other._valid = false;
    }

    ~EthRxTx();

    template <typename Elem, size_t Size>
    std::pair<uint16_t, RTEMbufArray<Elem, Size, MbufType::Pkt>> SendPackets(
        const uint16_t queue_id, RTEMbufArray<Elem, Size, MbufType::Pkt> &&pkts) {
        auto raw_ptr = reinterpret_cast<rte_mbuf **>(pkts.data());

        uint16_t nb_tx = rte_eth_tx_burst(portid, queue_id, raw_ptr, pkts.size());
        stats[queue_id].num_sent += nb_tx;

        auto [sent, free] = pkts.split(nb_tx);
        sent.release();
        return {nb_tx, std::move(free)};
    }

    template <typename Elem>
    uint16_t SendPacket(const uint16_t queue_id, RTEMbufElement<Elem, MbufType::Pkt> &&pkt) {
        rte_mbuf *raw_ptr = &pkt.get();
        pkt.release();

        auto nb_prepared = rte_eth_tx_prepare(portid, queue_id, &raw_ptr, 1);

        if (nb_prepared != 1) [[unlikely]] {
            return 0;
        }

        auto nb_tx = rte_eth_tx_burst(portid, queue_id, &raw_ptr, 1);

        stats[queue_id].num_sent += nb_tx;

        return nb_tx;
    }

    template <size_t Size>
    RTEMbufArray<DefaultPacket, Size, MbufType::Pkt> RcvPackets(const uint16_t queue_id) {
        const uint16_t max_rcv = Size;
        uint16_t nb_to_receive = max_rcv;

        std::array<RTEMbuf<DefaultPacket> *, Size> raw_pkts;
        auto nb_rx =
            rte_eth_rx_burst(portid, queue_id, (rte_mbuf **) &raw_pkts[0], nb_to_receive);

        RTEMbufArray<DefaultPacket, Size, MbufType::Pkt> pkts(
            std::span(raw_pkts.begin(), nb_rx));

        for (auto &pkt : pkts) {
            // Check if any of the checksums have to be computed in software
            if constexpr ((!opts::offload_rx_ipv4_cksum) ||
                      (!opts::offload_rx_l4_cksum)) {
                // Check for valid Ipv4 cksum when the L3 type is Ipv4
                uint32_t ptype_masked_l3 = pkt.packet_type & RTE_PTYPE_L3_MASK;
                uint32_t ptype_masked_l4 = pkt.packet_type & RTE_PTYPE_L4_MASK;

                if (ptype_masked_l3 == RTE_PTYPE_L3_IPV4 ||
                    ptype_masked_l3 == RTE_PTYPE_L3_IPV4_EXT ||
                    ptype_masked_l3 == RTE_PTYPE_L3_IPV4_EXT_UNKNOWN) {
                    UdpIpv4Header &udp_ipv4_hdr =
                        pkt.template data<UdpIpv4Header>();
                    rte_ipv4_hdr *ip_hdr = &udp_ipv4_hdr.ipv4_hdr;
                    void *l4_hdr = (void *) &udp_ipv4_hdr.udp_hdr;

                    // Packet is Ipv4
                    // Check Ipv4 checksum in software if not offloaded to
                    // hardware
                    if constexpr (!(opts::offload_rx_ipv4_cksum)) {
                        bool ipv4_chsum_bad =
                            (ptype_masked_l3 == RTE_PTYPE_L3_IPV4 ||
                            ptype_masked_l3 == RTE_PTYPE_L3_IPV4_EXT ||
                            ptype_masked_l3 ==
                                RTE_PTYPE_L3_IPV4_EXT_UNKNOWN) &&
                            rte_ipv4_cksum(ip_hdr);

                        pkt.ol_flags &= ~(RTE_MBUF_F_RX_IP_CKSUM_MASK);
                        pkt.ol_flags |= ipv4_chsum_bad
                                            ? RTE_MBUF_F_RX_IP_CKSUM_BAD
                                            : RTE_MBUF_F_RX_IP_CKSUM_GOOD;
                    }

                    if constexpr (!(opts::offload_rx_l4_cksum)) {
                        uint16_t cksum =
                            __rte_ipv4_udptcp_cksum(ip_hdr, l4_hdr);

                        bool l4_cksum_bad =
                            (ptype_masked_l4 == RTE_PTYPE_L4_UDP ||
                            ptype_masked_l4 == RTE_PTYPE_L4_TCP) &&
                            (cksum != 0xFFFF);

                        pkt.ol_flags &= ~(RTE_MBUF_F_RX_L4_CKSUM_MASK);
                        pkt.ol_flags |= l4_cksum_bad
                                            ? RTE_MBUF_F_RX_L4_CKSUM_BAD
                                            : RTE_MBUF_F_RX_L4_CKSUM_GOOD;
                    }
                } else if (ptype_masked_l3 == RTE_PTYPE_L3_IPV6 ||
                       ptype_masked_l3 == RTE_PTYPE_L3_IPV6_EXT ||
                       ptype_masked_l3 == RTE_PTYPE_L3_IPV6_EXT_UNKNOWN) {
                    // Packet is Ipv6
                    UdpIpv6Header &udp_ipv6_hdr =
                        pkt.template data<UdpIpv6Header>();

                    rte_ipv6_hdr *ip_hdr = &udp_ipv6_hdr.ipv6_hdr;
                    void *l4_hdr = (void *) &udp_ipv6_hdr.udp_hdr;

                    if constexpr (!(opts::offload_rx_l4_cksum)) {
                        uint16_t cksum =
                            __rte_ipv6_udptcp_cksum(ip_hdr, l4_hdr);
                        bool l4_cksum_bad =
                            (ptype_masked_l4 == RTE_PTYPE_L4_UDP ||
                            ptype_masked_l4 == RTE_PTYPE_L4_TCP) &&
                            (cksum != 0xFFFF);

                        pkt.ol_flags &= ~(RTE_MBUF_F_RX_L4_CKSUM_MASK);
                        pkt.ol_flags |= l4_cksum_bad
                                            ? RTE_MBUF_F_RX_L4_CKSUM_BAD
                                            : RTE_MBUF_F_RX_L4_CKSUM_GOOD;
                    }
                }
            }

            stats[queue_id].num_bad_ip_cksum +=
                ((pkt.ol_flags & RTE_MBUF_F_RX_IP_CKSUM_MASK) ==
                RTE_MBUF_F_RX_IP_CKSUM_BAD);
            stats[queue_id].num_bad_l4_cksum +=
                ((pkt.ol_flags & RTE_MBUF_F_RX_L4_CKSUM_MASK) ==
                RTE_MBUF_F_RX_L4_CKSUM_BAD);
        }

        stats[queue_id].num_total += pkts.size();

        return pkts;
    }

    template <typename Elem, size_t Size>
    void PreparePackets(const uint16_t queue_id,
        RTEMbufArray<Elem, Size, MbufType::Pkt> &to_prepare) {
        auto raw_ptr = reinterpret_cast<rte_mbuf **>(to_prepare.data());

        uint16_t nb_prepared =
            rte_eth_tx_prepare(portid, queue_id, raw_ptr, to_prepare.size());
        if (nb_prepared != to_prepare.size())
            spdlog::warn("{} packets prepared, {} requested", nb_prepared,
                to_prepare.size());
    }

    rte_ether_addr GetMacAddr() {
        return addr;
    }

    [[nodiscard]] tl::expected<void, std::string> GenerateDNSFlow(uint16_t queue_id, uint16_t src_port, uint16_t src_mask,
        uint16_t dst_port, uint16_t dst_mask, uint16_t dns_id, uint16_t dns_id_mask);

    void PrintStats();

    template <typename Elem, L3Type l3_type, L4Type l4_type>
    static inline void PreparePktCksums(RTEMbuf<Elem> &pkt) {
        // Flag is packet is ipv4 or ipv6, enable ipv4 tx checksum offload if set in opts
        constexpr uint64_t l3_offloads =
            (l3_type == L3Type::Ipv4 ? RTE_MBUF_F_TX_IPV4 : 0) |
            (l3_type == L3Type::Ipv6 ? RTE_MBUF_F_TX_IPV6 : 0) |
            (l3_type == L3Type::Ipv4 && opts::offload_tx_ipv4_cksum ? RTE_MBUF_F_TX_IP_CKSUM
                                        : 0);

        // Enable udp/tcp offload if set in opts
        constexpr uint64_t l4_offloads =
            (l4_type == L4Type::UDP && opts::offload_tx_l4_cksum ? RTE_MBUF_F_TX_UDP_CKSUM
                                     : 0) |
            (l4_type == L4Type::TCP && opts::offload_tx_l4_cksum ? RTE_MBUF_F_TX_TCP_CKSUM
                                     : 0);

        pkt.ol_flags |= (l3_offloads | l4_offloads);

        // Insert software ipv4 checksum if required
        if constexpr (l3_type == L3Type::Ipv4 && (!opts::offload_tx_ipv4_cksum)) {
            auto &ip_hdr = pkt.template data<Ipv4Header>();

            ip_hdr.ipv4_hdr.hdr_checksum = 0;
            ip_hdr.ipv4_hdr.hdr_checksum = rte_ipv4_cksum(&ip_hdr.ipv4_hdr);
        }

        // Insert Ipv4 UDP header if required
        if constexpr (l3_type == L3Type::Ipv4 && l4_type == L4Type::UDP &&
                  (!opts::offload_tx_l4_cksum)) {
            auto &udp_hdr = pkt.template data<UdpIpv4Header>();

            udp_hdr.udp_hdr.dgram_cksum = 0;
            udp_hdr.udp_hdr.dgram_cksum =
                rte_ipv4_udptcp_cksum(&udp_hdr.ipv4_hdr, (void *) &udp_hdr.udp_hdr);
        }

        // Insert Ipv4 TCP header if required
        if constexpr (l3_type == L3Type::Ipv4 && l4_type == L4Type::TCP &&
                  (!opts::offload_tx_l4_cksum)) {
            auto &tcp_hdr = pkt.template data<TcpIpv4Header>();

            tcp_hdr->tcp_hdr.cksum = 0;
            tcp_hdr->tcp_hdr.cksum =
                rte_ipv4_udptcp_cksum(&tcp_hdr.ipv4_hdr, (void *) &tcp_hdr.tcp_hdr);
        }

        // Insert Ipv6 UDP header if required
        if constexpr (l3_type == L3Type::Ipv6 && l4_type == L4Type::UDP &&
                  (!opts::offload_tx_l4_cksum)) {
            auto &udp_hdr = pkt.template data<UdpIpv6Header>();

            udp_hdr.udp_hdr.dgram_cksum = 0;
            udp_hdr.udp_hdr.dgram_cksum =
                rte_ipv6_udptcp_cksum(&udp_hdr.ipv6_hdr, (void *) &udp_hdr.udp_hdr);
        }

        // Insert Ipv6 TCP header if required
        if constexpr (l3_type == L3Type::Ipv6 && l4_type == L4Type::TCP &&
                  (!opts::offload_tx_l4_cksum)) {
            auto &tcp_hdr = pkt.template data<TcpIpv6Header>();

            tcp_hdr.tcp_hdr.cksum = 0;
            tcp_hdr.tcp_hdr.cksum =
                rte_ipv6_udptcp_cksum(&tcp_hdr.ipv6_hdr, (void *) &tcp_hdr.tcp_hdr);
        }
    }

    uint16_t GetPortId() const {
        return portid;
    }

private:
    EthRxTx(const EthDevConf &config, uint16_t portid, rte_ether_addr addr,
        std::vector<Stats> &&stats)
        : config(config),
          portid(portid),
          addr(addr),
          stats(stats),
          flow_configurations{},
          _valid{true} { }

    const EthDevConf config;

    const uint16_t portid;

    const struct rte_ether_addr addr;

    std::vector<Stats> stats;

    std::vector<rte_flow *> flow_configurations;

    bool _valid;

    static constexpr uint64_t dev_tx_offloads =
        (opts::offload_tx_ipv4_cksum ? RTE_ETH_TX_OFFLOAD_IPV4_CKSUM : 0) |
        (opts::offload_tx_l4_cksum
            ? (RTE_ETH_TX_OFFLOAD_UDP_CKSUM | RTE_ETH_TX_OFFLOAD_TCP_CKSUM)
            : 0) |
        (opts::offload_tx_mbuf_fast_free ? RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE : 0);

    static constexpr uint64_t dev_rx_offloads =
        (opts::offload_rx_ipv4_cksum ? RTE_ETH_RX_OFFLOAD_IPV4_CKSUM : 0) |
        (opts::offload_rx_l4_cksum
            ? (RTE_ETH_RX_OFFLOAD_UDP_CKSUM | RTE_ETH_RX_OFFLOAD_UDP_CKSUM)
            : 0);
};

#include "eth_rxtx_opts.h"