Program Listing for File eth_rxtx.cpp
↰ Return to documentation for file (src/eth_rxtx.cpp
)
#include "eth_rxtx.h"
#include <net/if.h>
#include <network_types.h>
#include <rte_errno.h>
#include <rte_ethdev.h>
#include <rte_ether.h>
#include <rte_ip.h>
#include <rte_pause.h>
#include <rte_udp.h>
#include <chrono>
#include <iostream>
#include <optional>
#include <thread>
#include "expected.h"
#include "expected_helpers.h"
#include "spdlog/spdlog.h"
#define MAX_PATTERN_NUM 5
std::optional<uint16_t> GetPortByName(std::string_view device_name) {
uint16_t portid;
if (rte_eth_dev_get_port_by_name(device_name.data(), &portid))
return std::nullopt;
return portid;
}
std::optional<rte_ether_addr> GetPortMac(const uint16_t portid) {
rte_ether_addr addr;
int retval = rte_eth_macaddr_get(portid, &addr);
if (retval)
return std::nullopt;
return addr;
}
void PrintOffloadFlags(rte_eth_dev_info &dev_info) {
spdlog::info("Available RX offloads:");
for (uint64_t flag = 1; flag; flag = flag << 1) {
if (dev_info.rx_offload_capa & flag)
spdlog::info("\t{}", rte_eth_dev_rx_offload_name(flag));
}
spdlog::info("Available TX offloads:");
for (uint64_t flag = 1; flag; flag = flag << 1) {
if (dev_info.tx_offload_capa & flag)
spdlog::info("\t{}", rte_eth_dev_tx_offload_name(flag));
}
}
template <class opts>
EthRxTx<opts>::~EthRxTx() {
if (!_valid)
return;
int res = rte_eth_dev_stop(portid);
if (res) {
spdlog::error("Cannot stop ethernet device: {}", rte_strerror(res));
return;
}
rte_eth_dev_close(portid);
}
template <class opts>
tl::expected<EthRxTx<opts>, std::string> EthRxTx<opts>::init(const EthDevConf &config,
std::string_view device_name, rte_mempool *mempool) {
const auto portid =
UNWRAP_OR_RETURN_ERR(GetPortByName(device_name), "Cannot get port by name");
const auto addr = UNWRAP_OR_RETURN_ERR(GetPortMac(portid), "Cannot get port MAC");
struct rte_eth_dev_info dev_info;
int retval = rte_eth_dev_info_get(portid, &dev_info);
if (retval != 0)
return tl::unexpected(
fmt::format("Failed to get device info: {}", rte_strerror(rte_errno)));
retval = rte_eth_stats_reset(portid);
if (retval != 0) {
spdlog::warn("Failed to reset ethernet stats: {}", retval);
spdlog::warn("Ethernet statistic might be inaccurate!");
}
PrintOffloadFlags(dev_info);
struct rte_eth_conf port_conf;
memset(&port_conf, 0, sizeof(struct rte_eth_conf));
spdlog::info("Driver name: {}", dev_info.driver_name);
// Check if all required tx offloads are available and print formatted error message if
// necessary
if ((dev_info.tx_offload_capa & dev_tx_offloads) != dev_tx_offloads) {
uint64_t not_available_offloads =
(dev_info.tx_offload_capa & dev_tx_offloads) ^ dev_tx_offloads;
// Exit and show which offloads are not available
std::string offload_msg = "Tx offloads ";
for (uint64_t flag = 1; flag; flag <<= 1) {
if (not_available_offloads & flag) {
offload_msg.append(rte_eth_dev_tx_offload_name(flag));
offload_msg.append(", ");
}
}
offload_msg.append("not available");
return tl::unexpected(offload_msg);
}
port_conf.txmode.offloads |= dev_tx_offloads;
// Check if all required tx offloads are available and print formatted error message if
// necessary
if ((dev_info.rx_offload_capa & dev_rx_offloads) != dev_rx_offloads) {
uint64_t not_available_offloads =
(dev_info.tx_offload_capa & dev_rx_offloads) ^ dev_rx_offloads;
// Exit and show which offloads are not available
std::string offload_msg = "Rx offloads ";
for (uint64_t flag = 1; flag; flag <<= 1) {
if (not_available_offloads & flag) {
offload_msg.append(rte_eth_dev_rx_offload_name(flag));
offload_msg.append(", ");
}
}
offload_msg.append("not available");
return tl::unexpected(offload_msg);
}
port_conf.rxmode.offloads |= dev_rx_offloads;
if (config.nb_rx_queues > dev_info.max_rx_queues)
return tl::unexpected(fmt::format("Max {} rx queues available, {} requested",
dev_info.max_rx_queues, config.nb_rx_queues));
if (config.nb_tx_queues > dev_info.max_tx_queues)
return tl::unexpected(fmt::format("Max {} tx queues available, {} requested",
dev_info.max_tx_queues, config.nb_tx_queues));
if (config.nb_tx_descrs < dev_info.tx_desc_lim.nb_min ||
config.nb_tx_descrs > dev_info.tx_desc_lim.nb_max)
return tl::unexpected(fmt::format("Invalid number of tx descriptors ({}), "
"must be in range {} to {}",
config.nb_tx_descrs, dev_info.tx_desc_lim.nb_min, dev_info.tx_desc_lim.nb_max));
if (config.nb_rx_descrs < dev_info.rx_desc_lim.nb_min ||
config.nb_rx_descrs > dev_info.rx_desc_lim.nb_max)
return tl::unexpected(fmt::format("Invalid number of rx descriptors ({}), "
"must be in range {} to {}",
config.nb_rx_descrs, dev_info.rx_desc_lim.nb_min, dev_info.rx_desc_lim.nb_max));
if (config.enable_rss) {
port_conf.rxmode.mq_mode = RTE_ETH_MQ_RX_RSS;
port_conf.rx_adv_conf.rss_conf.rss_hf = 0x7ef8;
}
// Descriptor limits already checked, not necessary
// retval = rte_eth_dev_adjust_nb_rx_tx_desc(portid, &config.nb_rx_descrs,
// &config.nb_tx_descrs); if (retval != 0) throw std::runtime_error("Cannot configure
// ethernet device");
spdlog::info("Configuring ethernet device");
/* Configure the Ethernet device. */
retval =
rte_eth_dev_configure(portid, config.nb_rx_queues, config.nb_tx_queues, &port_conf);
if (retval != 0)
return tl::unexpected(
fmt::format("Cannot configure ethernet device: {}", rte_strerror(rte_errno)));
struct rte_eth_rxconf rxconf;
rxconf = dev_info.default_rxconf;
/* Allocate and set up RX queus for Ethernet port. */
for (uint16_t q = 0; q < config.nb_rx_queues; q++) {
retval = rte_eth_rx_queue_setup(portid, q, config.nb_rx_descrs,
rte_eth_dev_socket_id(portid), &rxconf, mempool);
if (retval != 0)
return tl::unexpected(fmt::format("Cannot initialize rx queue {}: {}", q,
rte_strerror(rte_errno)));
}
struct rte_eth_txconf txconf;
txconf = dev_info.default_txconf;
/* Allocate and set up TX queus for Ethernet port. */
for (uint16_t q = 0; q < config.nb_tx_queues; q++) {
retval = rte_eth_tx_queue_setup(portid, q, config.nb_tx_descrs,
rte_eth_dev_socket_id(portid), &txconf);
if (retval != 0)
return tl::unexpected(fmt::format("Cannot initialize tx queue {}: {}", q,
rte_strerror(rte_errno)));
}
spdlog::info("Starting ethernet device");
/* Starting Ethernet port */
retval = rte_eth_dev_start(portid);
/* >8 End of starting of ethernet port. */
if (retval < 0)
return tl::unexpected(fmt::format("Cannot start ethernet port {}: {}", portid,
rte_strerror(rte_errno)));
// while (1) {
// rte_eth_link link_info{};
// retval = rte_eth_link_get(portid, &link_info);
// if (retval != 0)
// return tl::unexpected(fmt::format("Cannot get link info for port {}: {}",
// portid, rte_strerror(rte_errno)));
// if (link_info.link_status == RTE_ETH_LINK_UP)
// break;
// rte_pause();
// }
/* Display the port MAC address. */
printf("Port %u MAC: %02" PRIx8 " %02" PRIx8 " %02" PRIx8 " %02" PRIx8 " %02" PRIx8
" %02" PRIx8 "\n",
portid, RTE_ETHER_ADDR_BYTES(&addr));
/* Using promiscuous mode packets are received with any destination MAC */
/* Enable RX in promiscuous mode for the Ethernet device. */
retval = rte_eth_promiscuous_enable(portid);
if (retval != 0)
return tl::unexpected(fmt::format("Cannot enable promiscuous mode for port {}: {}",
portid, rte_strerror(rte_errno)));
return EthRxTx{config, portid, addr, std::vector<Stats>(RTE_MAX_LCORE)};
}
// TODO: move generating flow to a dedicated class, this function is i40e specific
template <class opts>
tl::expected<void, std::string> EthRxTx<opts>::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) {
rte_flow_attr attr;
rte_flow_item pattern[MAX_PATTERN_NUM];
rte_flow_action action[MAX_PATTERN_NUM];
rte_flow_action_queue queue = {.index = queue_id};
rte_flow_item_eth eth_spec;
rte_flow_item_eth eth_mask;
rte_flow_item_ipv4 ipv4_spec;
rte_flow_item_ipv4 ipv4_mask;
rte_flow_item_ipv6 ipv6_spec;
rte_flow_item_ipv6 ipv6_mask;
rte_flow_item_udp udp_spec;
rte_flow_item_udp udp_mask;
rte_flow_item_raw raw_spec;
rte_flow_item_raw raw_mask;
rte_flow_error error;
memset(pattern, 0, sizeof(pattern));
memset(action, 0, sizeof(action));
/*
* set the rule attribute.
* in this case only ingress packets will be checked.
*/
memset(&attr, 0, sizeof(struct rte_flow_attr));
attr.ingress = 1;
/*
* create the action sequence.
* one action only, move packet to queue
*/
action[0].type = RTE_FLOW_ACTION_TYPE_QUEUE;
action[0].conf = &queue;
// action[0].type = RTE_FLOW_ACTION_TYPE_DROP;
action[1].type = RTE_FLOW_ACTION_TYPE_END;
/*
* Set the first level of the pattern (eth).
* set to allow all
*/
memset(ð_spec, 0, sizeof(struct rte_flow_item_eth));
memset(ð_mask, 0, sizeof(struct rte_flow_item_eth));
eth_spec.type = 0;
eth_mask.type = 0;
pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH;
pattern[0].spec = ð_spec;
pattern[0].mask = ð_mask;
/*
* Setting the second level of the pattern (ipv4).
* Set to allow all
*/
memset(&ipv4_spec, 0, sizeof(struct rte_flow_item_ipv4));
memset(&ipv4_mask, 0, sizeof(struct rte_flow_item_ipv4));
ipv4_spec.hdr.dst_addr = 0;
ipv4_mask.hdr.dst_addr = 0;
ipv4_spec.hdr.src_addr = 0;
ipv4_mask.hdr.src_addr = 0;
pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4;
pattern[1].spec = &ipv4_spec;
pattern[1].mask = &ipv4_mask;
/*
* Setting the third level of the pattern (udp).
* Set the ports and masks as the user has setup
*/
memset(&udp_spec, 0, sizeof(struct rte_flow_item_udp));
memset(&udp_mask, 0, sizeof(struct rte_flow_item_udp));
udp_spec.hdr.src_port = rte_cpu_to_be_16(src_port);
udp_mask.hdr.src_port = rte_cpu_to_be_16(src_mask);
udp_spec.hdr.dst_port = rte_cpu_to_be_16(dst_port);
udp_mask.hdr.dst_port = rte_cpu_to_be_16(dst_mask);
pattern[2].type = RTE_FLOW_ITEM_TYPE_UDP;
pattern[2].spec = &udp_spec;
pattern[2].mask = &udp_mask;
memset(&raw_spec, 0, sizeof(struct rte_flow_item_raw));
memset(&raw_mask, 0, sizeof(struct rte_flow_item_raw));
const rte_be16_t dns_id_be = rte_cpu_to_be_16(dns_id);
const rte_be16_t dns_id_mask_be = rte_cpu_to_be_16(dns_id_mask);
const uint8_t *search_pattern_spec = (uint8_t *) &dns_id_be;
const uint8_t *search_pattern_mask = (uint8_t *) &dns_id_mask_be;
raw_spec.relative = 1;
raw_spec.search = 0;
raw_spec.reserved = 0;
raw_spec.offset = 0;
raw_spec.limit = 0;
raw_spec.length = 2;
raw_spec.pattern = search_pattern_spec;
raw_mask.relative = 1;
raw_mask.search = 1;
raw_mask.reserved = 0x3FFFFFFF;
raw_mask.offset = 0xFFFFFFFF;
raw_mask.limit = 0xFFFF;
raw_mask.length = 0xFFFF;
raw_mask.pattern = search_pattern_mask;
pattern[3].type = RTE_FLOW_ITEM_TYPE_RAW;
pattern[3].spec = &raw_spec;
pattern[3].mask = &raw_mask;
/* the final level must be always type end */
pattern[4].type = RTE_FLOW_ITEM_TYPE_END;
int res = rte_flow_validate(portid, &attr, pattern, action, &error);
if (res) {
return tl::unexpected(
fmt::format("Failed to validate ipv4 flow rule: {}", error.message));
}
flow_configurations.push_back(rte_flow_create(portid, &attr, pattern, action, &error));
if (flow_configurations.back() == NULL) {
return tl::unexpected(fmt::format("Failed to create ipv4 flow rule"));
}
/*
* Setting the new second level of the pattern (ipv6).
* Set to allow all
*/
memset(&ipv6_spec, 0, sizeof(struct rte_flow_item_ipv6));
memset(&ipv6_mask, 0, sizeof(struct rte_flow_item_ipv6));
pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV6;
pattern[1].spec = &ipv6_spec;
pattern[1].mask = &ipv6_mask;
res = rte_flow_validate(portid, &attr, pattern, action, &error);
if (res) {
return tl::unexpected(
fmt::format("Failed to validate ipv6 flow rule: ", error.message));
}
flow_configurations.push_back(rte_flow_create(portid, &attr, pattern, action, &error));
if (flow_configurations.back() == NULL) {
return tl::unexpected(fmt::format("Failed to create ipv6 flow rule"));
}
// Success
return {};
}
template <class opts>
void EthRxTx<opts>::PrintStats() {
rte_eth_stats port_stats;
rte_eth_stats_get(portid, &port_stats);
spdlog::info("RX bytes: {}", port_stats.ibytes);
spdlog::info("RX packets: {}", port_stats.ipackets);
spdlog::info("RX errors: {}", port_stats.ierrors);
spdlog::info("TX bytes: {}", port_stats.obytes);
spdlog::info("TX packets: {}", port_stats.opackets);
spdlog::info("TX errors: {}", port_stats.oerrors);
Stats total_stats = {0, 0, 0, 0, 0, 0};
for (const Stats &q_stats : stats) {
total_stats.num_total += q_stats.num_total;
total_stats.num_bad_ip_cksum += q_stats.num_bad_ip_cksum;
total_stats.num_bad_l4_cksum += q_stats.num_bad_l4_cksum;
total_stats.num_sent += q_stats.num_sent;
total_stats.num_bytes_sent += q_stats.num_bytes_sent;
}
spdlog::info("Total Mpackets received: {}",
static_cast<double>(total_stats.num_total) / 1e6);
spdlog::info("Total Mpackets sent: {}", static_cast<double>(total_stats.num_sent) / 1e6);
spdlog::info("Total Gbytes sent: {}",
static_cast<double>(total_stats.num_bytes_sent) / 1e9);
spdlog::info("Bad IP cksum: {}", total_stats.num_bad_ip_cksum);
spdlog::info("Bad L4 cksum: {}", total_stats.num_bad_l4_cksum);
}
#include "eth_rxtx_opts.h"
template class EthRxTx<NIC_OPTS>;