* Copyright (c) Huawei Technologies Co., Ltd. 2020-2021. All rights reserved.
* gazelle is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#include <rte_ethdev.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <ifaddrs.h>
#include <lwip/lwipgz_posix_api.h>
#include <lwip/dpdk_version.h>
#include <linux/ipv6.h>
#include <securec.h>
#include "lstack_cfg.h"
#include "lstack_log.h"
#include "lstack_port_map.h"
#include "lstack_interrupt.h"
#include "lstack_virtio.h"
#include "mbox_ring.h"
#define VIRTIO_USER_NAME "virtio_user"
#define VIRTIO_DPDK_PARA_LEN 256
#define VIRTIO_TX_RX_RING_SIZE 1024
#define VIRTIO_NETIF_CHECK_MAX_TIMES 10
#define VIRTIO_NETIF_CMD_OUTPUT 4096
#define VIRTIO_MASK_BITS(mask) (32 - __builtin_clz(mask))
static struct virtio_instance g_virtio_instance = {0};
static char g_virtio_user_name[IFNAMSIZ] = {0};
struct virtio_instance* virtio_instance_get(void)
{
return &g_virtio_instance;
}
static int virtio_set_ipv6_addr(void)
{
struct cfg_params *cfg = get_global_cfg_params();
struct ifreq ifr;
memset_s(&ifr, sizeof(ifr), 0, sizeof(ifr));
int sockfd = posix_api->socket_fn(AF_INET6, SOCK_DGRAM, 0);
if (sockfd < 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_set_ipv6_addr failed ret =%d errno=%d \n", sockfd, errno);
return -1;
}
int ret = strncpy_s(ifr.ifr_name, sizeof(ifr.ifr_name), g_virtio_user_name, strlen(g_virtio_user_name));
if (ret != 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_set_ipv6_addr strncpy failed ret =%d errno=%d \n", ret, errno);
posix_api->close_fn(sockfd);
return -1;
}
ret = posix_api->ioctl_fn(sockfd, SIOGIFINDEX, &ifr);
if (ret < 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_set_ipv6_addr failed ret =%d errno=%d \n", ret, errno);
posix_api->close_fn(sockfd);
return -1;
}
struct in6_ifreq ifr6;
memset_s(&ifr6, sizeof(ifr6), 0, sizeof(ifr6));
ret = memcpy_s(&ifr6.ifr6_addr, sizeof(ifr6.ifr6_addr), &(cfg->host_addr6), sizeof((cfg->host_addr6.addr)));
if (ret != 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_set_ipv6_addr memcpy_s failed ret =%d errno=%d \n", ret, errno);
posix_api->close_fn(sockfd);
return -1;
}
ifr6.ifr6_ifindex = ifr.ifr_ifindex;
ifr6.ifr6_prefixlen = VIRTIO_MASK_BITS(cfg->netmask.addr);
ret = posix_api->ioctl_fn(sockfd, SIOCSIFADDR, &ifr6);
if (ret < 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_set_ipv6_addr failed err= %d errno %d \n", ret, errno);
posix_api->close_fn(sockfd);
return -1;
}
posix_api->close_fn(sockfd);
return 0;
}
static int virtio_set_ipv4_addr(void)
{
int ret = 0;
int sockfd = posix_api->socket_fn(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_set_ipv6_addr failed ret= %d errno %d \n", sockfd, errno);
return -1;
}
struct cfg_params *cfg = get_global_cfg_params();
struct ifreq ifr;
memset_s(&ifr, sizeof(ifr), 0, sizeof(ifr));
ret = strcpy_s(ifr.ifr_name, sizeof(ifr.ifr_name), g_virtio_user_name);
if (ret != 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_set_ipv4_addr strcpy_s failed ret=%d errno %d \n", ret, errno);
posix_api->close_fn(sockfd);
return -1;
}
struct sockaddr_in *addr = (struct sockaddr_in *)&ifr.ifr_addr;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = cfg->host_addr.addr;
if (posix_api->ioctl_fn(sockfd, SIOCSIFADDR, &ifr) < 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_cfg_ip failed errno %d \n", errno);
posix_api->close_fn(sockfd);
return -1;
}
addr->sin_addr.s_addr = cfg->netmask.addr;
if (posix_api->ioctl_fn(sockfd, SIOCSIFNETMASK, &ifr) < 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_cfg_ip netmask=%u fail\n", cfg->netmask.addr);
posix_api->close_fn(sockfd);
return -1;
}
posix_api->close_fn(sockfd);
return 0;
}
static int virtio_ipv6_is_tentative(void)
{
FILE *fp;
char output[VIRTIO_NETIF_CMD_OUTPUT] = {0};
int not_ready = 0;
const char *cmd = "ip a |grep tentative";
fp = popen(cmd, "r");
if (fp == NULL) {
LSTACK_LOG(ERR, LSTACK, "%s execution failed \n", cmd);
return -1;
}
if (fgets(output, sizeof(output), fp) != NULL) {
not_ready = 1;
}
pclose(fp);
return not_ready;
}
static int virtio_netif_up(void)
{
int sockfd = posix_api->socket_fn(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_netif_up socket_fn failed ret= %d errno %d \n", sockfd, errno);
return -1;
}
struct ifreq ifr;
memset_s(&ifr, sizeof(ifr), 0, sizeof(ifr));
int ret = strcpy_s(ifr.ifr_name, sizeof(ifr.ifr_name), g_virtio_user_name);
if (ret != 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_netif_up strcpy_s failed ret=%d errno %d \n", ret, errno);
posix_api->close_fn(sockfd);
return -1;
}
ifr.ifr_flags |= IFF_UP;
if (posix_api->ioctl_fn(sockfd, SIOCSIFFLAGS, &ifr) < 0) {
posix_api->close_fn(sockfd);
LSTACK_LOG(ERR, LSTACK, " virtio_netif_up ioctl_fn failed errno= %d \n", errno);
return -1;
}
posix_api->close_fn(sockfd);
* For virtio_user IPv6 addresses, the kernel will check if they are valid,
* so wait a few seconds for the address status to change from scope global tentative to scope global.
*/
for (int i = 0; i < VIRTIO_NETIF_CHECK_MAX_TIMES; i++) {
ret = virtio_ipv6_is_tentative();
if (ret == 0) {
break;
} else if (ret < 0) {
return -1;
}
sleep(1);
}
return 0;
}
static int virtio_cfg_ip(void)
{
struct cfg_params *cfg = get_global_cfg_params();
if (virtio_set_ipv4_addr() < 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_set_ipv4_addr failed \n");
return -1;
}
if (!ip6_addr_isany(&cfg->host_addr6) && virtio_set_ipv6_addr() < 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_set_ipv6_addr failed \n");
return -1;
}
if (virtio_netif_up() < 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_netif_up failed \n");
return -1;
}
return 0;
}
void virtio_tap_process_rx(uint16_t port, uint32_t queue_id)
{
struct rte_mbuf *pkts_burst[VIRTIO_TX_RX_RING_SIZE];
uint16_t lstack_net_port = port;
uint32_t pkg_num;
pkg_num = rte_eth_rx_burst(g_virtio_instance.virtio_port_id, queue_id, pkts_burst, VIRTIO_TX_RX_RING_SIZE);
* For VLAN, the tap device defaults to tx-vlan-ofload as enabled and will not be modified by default,
* so the judgment is skipped.
* For checksum, tap devices are turned off by default, and it is assumed that they will not be modified,
* so no action will be taken.
* For TSO, tap devices do not support it, so no action will be taken.
*/
if (get_global_cfg_params()->vlan_mode != -1) {
for (int i = 0; i< pkg_num; i++) {
pkts_burst[i]->ol_flags |= RTE_MBUF_F_TX_VLAN;
pkts_burst[i]->vlan_tci = (u16_t)get_global_cfg_params()->vlan_mode;
}
}
if (pkg_num > 0) {
g_virtio_instance.rx_pkg[queue_id] += pkg_num;
uint16_t nb_rx = rte_eth_tx_burst(lstack_net_port, queue_id, pkts_burst, pkg_num);
for (uint16_t i = nb_rx; i < pkg_num; ++i) {
rte_pktmbuf_free(pkts_burst[i]);
g_virtio_instance.rx_drop[queue_id]++;
}
}
}
void virtio_tap_process_tx(uint16_t queue_id, struct rte_mbuf *mbuf_copy)
{
int tx_num = rte_eth_tx_burst(g_virtio_instance.virtio_port_id, queue_id, &(mbuf_copy), 1);
if (tx_num < 0) {
rte_pktmbuf_free(mbuf_copy);
g_virtio_instance.tx_drop[queue_id]++;
LSTACK_LOG(ERR, LSTACK, "virtio_tap_process_tx failed %d, %d\n", queue_id, tx_num);
}
g_virtio_instance.tx_pkg[queue_id]++;
}
static int virtio_port_init(uint16_t port)
{
int retval;
uint16_t rx_queue_num = g_virtio_instance.rx_queue_num;
uint16_t tx_queue_num = g_virtio_instance.tx_queue_num;
int mbuf_total_num = get_global_cfg_params()->num_cpu * get_global_cfg_params()->num_process;
LSTACK_LOG(INFO, LSTACK, "virtio_port_init port= %u rx_queue_num=%u tx_queue_num=%u \n",
port, rx_queue_num, tx_queue_num);
struct rte_eth_conf port_conf;
memset(&port_conf, 0, sizeof(struct rte_eth_conf));
struct rte_eth_dev_info dev_info;
retval = rte_eth_dev_info_get(port, &dev_info);
if (retval != 0) {
LSTACK_LOG(ERR, LSTACK, "rte_eth_dev_info_get failed(port %u) info: %d\n", port, retval);
return retval;
}
port_conf.intr_conf.rxq = get_global_cfg_params()->stack_interrupt;
retval = rte_eth_dev_configure(port, rx_queue_num, tx_queue_num, &port_conf);
if (retval != 0) {
LSTACK_LOG(ERR, LSTACK, "rte_eth_dev_configure failed retval=%d\n", retval);
return retval;
}
for (uint16_t q = 0; q < tx_queue_num; q++) {
retval = rte_eth_tx_queue_setup(port, q, VIRTIO_TX_RX_RING_SIZE,
rte_eth_dev_socket_id(port), NULL);
if (retval < 0) {
LSTACK_LOG(ERR, LSTACK, "rte_eth_tx_queue_setup failed (queue %u) retval=%d \n", q, retval);
return retval;
}
}
for (uint16_t q = 0; q < rx_queue_num; q++) {
struct rte_mempool *rxtx_mbuf_pool = mem_get_mbuf_pool(q % mbuf_total_num);
retval = rte_eth_rx_queue_setup(port, q, VIRTIO_TX_RX_RING_SIZE, rte_eth_dev_socket_id(port),
NULL, rxtx_mbuf_pool);
if (retval < 0) {
LSTACK_LOG(ERR, LSTACK, "rte_eth_rx_queue_setup failed (queue %u) retval=%d \n", q, retval);
return retval;
}
if (port_conf.intr_conf.rxq) {
struct intr_dpdk_event_args intr_arg;
intr_arg.port_id = port;
intr_arg.queue_id = q;
intr_register(q, INTR_DPDK_EVENT, &intr_arg);
}
}
return 0;
}
static int32_t virtio_port_start(uint16_t virtio_port)
{
int retval = 0;
if (virtio_port_init(virtio_port) < 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_port_init failed \n");
return -1;
}
retval = rte_eth_dev_start(virtio_port);
if (retval < 0) {
LSTACK_LOG(ERR, LSTACK, "rte_eth_dev_start failed retval=%d\n", retval);
return retval;
}
if (virtio_cfg_ip() != 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_cfg_ip_mac failed\n");
return -1;
}
LSTACK_LOG(INFO, LSTACK, "virtio_user lstack_net_port=%u virtio_port=%u rx_queue_num = %u tx_queue_num = %u\n",
g_virtio_instance.lstack_port_id, g_virtio_instance.virtio_port_id,
g_virtio_instance.rx_queue_num, g_virtio_instance.tx_queue_num);
return 0;
}
static int virtio_get_netif_num(void)
{
int netif_num = 0;
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) {
LSTACK_LOG(ERR, LSTACK, "getifaddrs failed \n");
return -1;
}
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (strncmp(ifa->ifa_name, VIRTIO_USER_NAME, sizeof(VIRTIO_USER_NAME) - 1) == 0) {
netif_num++;
}
}
freeifaddrs(ifaddr);
return netif_num;
}
int virtio_port_create(int lstack_net_port)
{
char portargs[VIRTIO_DPDK_PARA_LEN] = {0};
struct rte_ether_addr addr;
uint16_t virtio_port_id = 0xffff;
struct rte_eth_dev_info dev_info;
int ret = virtio_get_netif_num();
if (ret < 0) {
return -1;
}
ret = sprintf_s(g_virtio_user_name, sizeof(g_virtio_user_name), VIRTIO_USER_NAME "%d", ret);
if (ret < 0) {
LSTACK_LOG(ERR, LSTACK, "sprintf_s failed ret=%d \n", ret);
return -1;
}
ret = rte_eth_dev_info_get(lstack_net_port, &dev_info);
if (ret != 0) {
LSTACK_LOG(ERR, LSTACK, "get dev info ret=%d\n", ret);
return ret;
}
g_virtio_instance.rx_queue_num = dev_info.nb_rx_queues;
g_virtio_instance.tx_queue_num = dev_info.nb_tx_queues;
if (g_virtio_instance.rx_queue_num > VIRTIO_MAX_QUEUE_NUM ||
g_virtio_instance.tx_queue_num > VIRTIO_MAX_QUEUE_NUM) {
LSTACK_LOG(ERR, LSTACK, "virtio_port_create failed queue_num (%u %u) is bigger than %u\n",
g_virtio_instance.rx_queue_num, g_virtio_instance.tx_queue_num, VIRTIO_MAX_QUEUE_NUM);
return -1;
}
int retval = rte_eth_macaddr_get(lstack_net_port, &addr);
if (retval != 0) {
LSTACK_LOG(ERR, LSTACK, " rte_eth_macaddr_get failed ret = %d\n", retval);
return retval;
}
uint16_t actual_queue_num = (g_virtio_instance.rx_queue_num < g_virtio_instance.tx_queue_num) ?
g_virtio_instance.rx_queue_num : g_virtio_instance.tx_queue_num;
retval = snprintf(portargs, sizeof(portargs),
"path=/dev/vhost-net,queues=%u,queue_size=%u,iface=%s,mac=" RTE_ETHER_ADDR_PRT_FMT,
actual_queue_num, VIRTIO_TX_RX_RING_SIZE, g_virtio_user_name, RTE_ETHER_ADDR_BYTES(&addr));
if (retval < 0) {
LSTACK_LOG(ERR, LSTACK, "virtio portargs snprintf failed ret=%d \n", retval);
return retval;
}
LSTACK_LOG(INFO, LSTACK, "virtio portargs=%s \n", portargs);
retval = rte_eal_hotplug_add("vdev", g_virtio_user_name, portargs);
if (retval < 0) {
LSTACK_LOG(ERR, LSTACK, "rte_eal_hotplug_add failed retval=%d : %s\n", retval, strerror(-retval));
return retval;
}
retval = rte_eth_dev_get_port_by_name(g_virtio_user_name, &virtio_port_id);
if (retval != 0) {
rte_eal_hotplug_remove("vdev", g_virtio_user_name);
LSTACK_LOG(ERR, LSTACK, "virtio_user0 not found\n");
return -1;
}
g_virtio_instance.virtio_port_id = virtio_port_id;
g_virtio_instance.lstack_port_id = lstack_net_port;
retval = virtio_port_start(virtio_port_id);
if (retval != 0) {
LSTACK_LOG(ERR, LSTACK, "virtio_port_start failed ret=%d\n", retval);
rte_eal_hotplug_remove("vdev", g_virtio_user_name);
return retval;
}
return 0;
}
bool virtio_distribute_pkg_to_kernel(uint16_t dst_port)
{
if (dst_port == VIRTIO_PORT_INVALID) {
return false;
}
return (port_map_get(dst_port) == 0);
}