VPP支持网卡热插拔了...

2023-11-17 20:45:29 浏览数 (3)

VPP的DPDK插件默认是不支持动态加载网卡的,需要在启动前在配置文件startup.conf设置网卡的PCI编号。也就是不支持网卡的热插拔动作。网卡热插拔主要解决高可靠性持续不间断运行的问题。在某些特殊的应用场合,如服务器、数据中心等,可能需要不关闭系统电源的情况下更换网卡。

最近社区提交了一个patch支持dpdk网卡热插拔功能,下面就来环境上实验一下:

此提交尚未合入主线,需要手动打补丁,提交链接: https://gerrit.fd.io/r/c/vpp/ /39121

下面是实验环境上存在2个virto-pci网卡,其中00:02.0已经作为管理口在用,我们可以将网卡0000:00:06.0动态加载到vpp上。

代码语言:javascript复制
root@jinsh:~/workspace/vpp/build-root/install-vpp-native/external/bin# ./dpdk-devbind.py -s

Network devices using kernel driver
===================================
0000:00:02.0 'Virtio network device 1000' if=ens2 drv=virtio-pci unused=vfio-pci,uio_pci_generic *Active*
0000:00:06.0 'Virtio network device 1000' if=ens6 drv=virtio-pci unused=vfio-pci,uio_pci_generic 

vppctl命令登录vpp命令行视图下,执行“create interface dpdk”命令完成网卡动态加载。

代码语言:javascript复制
#1、加载网卡。需要注意的PCI 编译需要写全格式,否则无法识别。
vpp# create interface dpdk 00:00:06.0 num-rx-desc 1024 num-tx-desc 1024
GigabitEthernet0/6/0
#2、接口设置接口up,并设置dhcp client模式。
vpp# set interface state GigabitEthernet0/6/0 up
vpp# set dhcp client intfc GigabitEthernet0/6/0 
#3、查询接口已获取到ip地址,ping网关也正常。
vpp# show interface address
GigabitEthernet0/6/0 (up):
  L3 192.168.100.133/24
local0 (dn):
vpp# ping 192.168.100.1
116 bytes from 192.168.100.1: icmp_seq=1 ttl=64 time=.1632 ms
116 bytes from 192.168.100.1: icmp_seq=2 ttl=64 time=.0990 ms
116 bytes from 192.168.100.1: icmp_seq=3 ttl=64 time=.0898 ms
116 bytes from 192.168.100.1: icmp_seq=4 ttl=64 time=.0911 ms
Aborted due to a keypress.

Statistics: 4 sent, 4 received, 0% packet loss
代码语言:javascript复制
代码语言:javascript复制
执行“delete interface dpdk”可以将网卡从vpp中卸载掉。
代码语言:javascript复制
代码语言:javascript复制
vpp# delete interface dpdk GigabitEthernet0/6/0 
vpp# show interface 
              Name               Idx    State  MTU (L3/IP4/IP6/MPLS)     Counter          Count     
local0                            0     down          0/0/0/0       
vpp# 
代码语言:javascript复制
下面是网卡加载和卸载的函数,代码相当简洁,如下:
代码语言:javascript复制
int
dpdk_create_if (vlib_main_t *vm, dpdk_create_if_args_t *args)
{
  u8 *pci_addr = 0;
  dpdk_main_t *dm = &dpdk_main;
  vnet_main_t *vnm = vnet_get_main ();
  u16 port_id = ~0;
  args->rv = 0;
  args->error = NULL;
  dpdk_device_t *xd = NULL;

  vec_reset_length (pci_addr);
  pci_addr =
    format (0, "%U%c", format_vlib_pci_addr, &args->config.pci_addr, 0);
  /*查询当前网卡是否已经被dpdk纳管*/
  args->rv = rte_eth_dev_get_port_by_name ((char *) pci_addr, &port_id);
  if (!args->rv && vec_len (dm->devices))
    {
      vec_foreach (xd, dm->devices)
    {
      if (xd->port_id == port_id)
        {
          goto cleanup;
        }
    }
    }
 /*网卡绑定UIO*/
  args->error =
    vlib_pci_bind_to_uio (vm, &args->config.pci_addr, (char *) "auto", 1);
  if (args->error)
    {
      args->rv = args->error->code;
      goto cleanup;
    }
  /*执行网卡热加载函数*/
  args->rv = rte_eal_hotplug_add ("pci", (char *) pci_addr, NULL);
  if (args->rv)
    {
      args->error = clib_error_return (0, "rte_eal_hotplug_add failed");
      goto cleanup;
    }
  /*查询网卡port id*/
  args->rv = rte_eth_dev_get_port_by_name ((char *) pci_addr, &port_id);
  if (args->rv)
    {
      args->error =
    clib_error_return (0, "rte_eth_dev_get_port_by_name failed");
      goto cleanup;
    }
  /*根据实际workers数量更新rx tx队列*/
  if (args->config.workers && args->config.num_rx_queues == 0)
    args->config.num_rx_queues =
      clib_bitmap_count_set_bits (args->config.workers);
  else if (args->config.workers &&
       clib_bitmap_count_set_bits (args->config.workers) !=
         args->config.num_rx_queues)
    {
      args->error =
    clib_error_return (0,
               "%U: number of worker threads must be "
               "equal to number of rx queues",
               format_vlib_pci_addr, &args->config.pci_addr);
      goto cleanup;
    }
  /*在vpp中创建网卡接口*/
  u32 hw_if_index = dpdk_port_init (dm, port_id, &args->config);
  vnet_hw_interface_t *hif = vnet_get_hw_interface (vnm, hw_if_index);
  args->sw_if_index = hif->sw_if_index;

  xd = vec_elt_at_index (dm->devices, hif->dev_instance);

  vnet_hw_if_update_runtime_data (vnm, hw_if_index);
  /*更新网卡状态*/
  f64 now = vlib_time_now (vm);
  dpdk_update_link_state (xd, now);

cleanup:
  vec_free (pci_addr);

  return args->rv;
}

int
dpdk_delete_if (vlib_main_t *vm, u32 sw_if_index)
{
  int rv = 0;
  dpdk_main_t *dm = &dpdk_main;
  vnet_main_t *vnm = vnet_get_main ();
  struct rte_eth_dev_info di = {};

  if (sw_if_index == ~0)
    {
      dpdk_log_warn ("[%u] invalid sw interface.", sw_if_index);
      return VNET_ERR_INVALID_SW_IF_INDEX;
    }

  vnet_sw_interface_t *si = vnet_get_sw_interface (vnm, sw_if_index);
  if (!si)
    {
      dpdk_log_warn ("[%u] failed to get sw interface.", sw_if_index);
      return VNET_ERR_INVALID_SW_IF_INDEX;
    }

  vnet_hw_interface_t *hif = vnet_get_hw_interface (vnm, si->hw_if_index);
  if (!hif)
    {
      dpdk_log_warn ("[%u] failed to get hw interface.", si->hw_if_index);
      return VNET_ERR_NO_SUCH_ENTRY;
    }

  dpdk_device_t *xd = vec_elt_at_index (dm->devices, hif->dev_instance);
  if (!xd)
    {
      dpdk_log_warn ("[%u] failed to get dpdk device.", hif->dev_instance);
      return VNET_ERR_NO_SUCH_ENTRY;
    }
  /*删除接口*/
  ethernet_delete_interface (vnm, si->hw_if_index);
  /*网卡设备stop掉*/
  dpdk_device_stop (xd);

  if ((rv = rte_eth_dev_info_get (xd->port_id, &di)) != 0)
    {
      dpdk_log_warn ("[%u] failed to get device info. skipping device.",
             xd->port_id);
      return VNET_ERR_NO_SUCH_ENTRY;
    }
  /*将网卡卸载掉*/
  rv = rte_dev_remove (di.device);
  if (rv)
    {
      dpdk_log_warn ("rte_dev_remove failed.");
      return VNET_ERR_NO_SUCH_ENTRY;
    }

  vec_del1 (dm->devices, xd - dm->devices);

  return 0;
}
代码语言:javascript复制
代码语言:javascript复制

此插件在vpp不重启的情况下已经实现了网卡加载和卸载的基本功能接口,但是并不是和系统热插拔事件相关联的。插件功能并不是很完善,当网卡绑定其他资源(子接口、l2xc)等功能时,卸载网卡可能会导致vpp异常,还需要释放相应的资源。

参考资料:

1、https://blog.csdn.net/longyu_wlz/article/details/127243979 2、https://zhuanlan.zhihu.com/p/560645934

0 人点赞