Cisco VPP 网络接口注册分析

简介

Vpp在实际应用场景中专注于网络数据包的处理,收发数据包的过程一般由第三方软件提供如:DPDK、NETMAP等,但自身也提供了丰富的虚拟网络接口,如:Virtio、Vxlan、Ipsex等。每种接口,不论是虚拟的还是物理的在启动的时候都会主动向Vpp注册自己的接口。

DPDK接口注册分析

VPP编译过程中可以选择是否编译DPDK的IGB_UIO驱动,默认是不编译的,编译IGB_UIO驱动方法:

diff --git a/build/external/packages/dpdk.mk b/build/external/packages/dpdk.mk
index 7e8af07..afe32fd 100644
--- a/build/external/packages/dpdk.mk
+++ b/build/external/packages/dpdk.mk
@@ -229,7 +229,7 @@ $(B)/custom-config: $(B)/.dpdk-patch.ok Makefile
        $(call set,RTE_LIBRTE_BBDEV_NULL,n)
        $(call set,RTE_TEST_PMD,n)
        $(call set,RTE_KNI_KMOD,n)
-       $(call set,RTE_EAL_IGB_UIO,n)
+       $(call set,RTE_EAL_IGB_UIO,y)
        @# currently broken in 18.02
        $(call set,RTE_LIBRTE_DPAA_BUS,n)
        $(call set,RTE_LIBRTE_DPAA_MEMPOOL,n)

DPDK接口使用Plugin的方式注册到VPP环境,DPDK Plugin在启动时首先使用“VNET_DEVICE_CLASS”宏定义初始化DPDK结构体

/* *INDENT-OFF* */
VNET_DEVICE_CLASS (dpdk_device_class) = {
  .name = "dpdk",
  .tx_function_n_errors = DPDK_TX_FUNC_N_ERROR,
  .tx_function_error_strings = dpdk_tx_func_error_strings,
  .format_device_name = format_dpdk_device_name,
  .format_device = format_dpdk_device,
  .format_tx_trace = format_dpdk_tx_trace,
  .clear_counters = dpdk_clear_hw_interface_counters,
  .admin_up_down_function = dpdk_interface_admin_up_down,
  .subif_add_del_function = dpdk_subif_add_del_function,
  .rx_redirect_to_node = dpdk_set_interface_next_node,
  .mac_addr_change_function = dpdk_set_mac_address,
  .format_flow = format_dpdk_flow,
  .flow_ops_function = dpdk_flow_ops_fn,
};
/* *INDENT-ON* */

DPDK是作为插件的方式和VPP对接,VNET_DEVICE_CLASS宏定义使用__attribute__的__constructor__方法让DPDK插件在Load的时候首先执行指定函数,所以在DPDK插件刚加入到VPP时又VNET_DEVICE_CLASS宏定义修饰的函数就会被调用。

VNET_DEVICE_CLASS注册设备的过程分为两个动作,第一个是注册,把函数加入到vnm->device_class_registrations链表;第二个是实现卸载函数,这个函数作用是把注册到vnm->device_class_registrations链表的函数删除

#define VNET_DEVICE_CLASS(x,...)                                        \
  __VA_ARGS__ vnet_device_class_t x;                                    \
static void __vnet_add_device_class_registration_##x (void)             \
    __attribute__((__constructor__)) ;                                  \
static void __vnet_add_device_class_registration_##x (void)             \
{                                                                       \
    vnet_main_t * vnm = vnet_get_main();                                \
    x.next_class_registration = vnm->device_class_registrations;        \
    vnm->device_class_registrations = &x;                               \
}                                                                       \
static void __vnet_rm_device_class_registration_##x (void)              \
    __attribute__((__destructor__)) ;                                   \
static void __vnet_rm_device_class_registration_##x (void)              \
{                                                                       \
    vnet_main_t * vnm = vnet_get_main();                                \
    VLIB_REMOVE_FROM_LINKED_LIST (vnm->device_class_registrations,      \
                                  &x, next_class_registration);         \
}                                                                       \
__VA_ARGS__ vnet_device_class_t x

同理,VNET_HW_INTERFACE_CLASS方法也是用来初始化接口

Vpp启动过程中会在vnet_interface_init函数出遍历以上两个宏定义中的链表初始化Vpp接口

{
    vnet_device_class_t *c;

    c = vnm->device_class_registrations;

    while (c)
      {
	c->index = vec_len (im->device_classes);
	hash_set_mem (im->device_class_by_name, c->name, c->index);

	if (c->tx_fn_registrations)
	  {
	    vlib_node_fn_registration_t *fnr = c->tx_fn_registrations;
	    int priority = -1;
................
................
{
    vnet_hw_interface_class_t *c;

    c = vnm->hw_interface_class_registrations;

    while (c)
      {
	c->index = vec_len (im->hw_interface_classes);
	hash_set_mem (im->hw_interface_class_by_name, c->name, c->index);

	if (NULL == c->build_rewrite)
	  c->build_rewrite = default_build_rewrite;
	if (NULL == c->update_adjacency)
	  c->update_adjacency = default_update_adjacency;

DPDK接口方法注册分析

VPP在使用DPDK接口时会根据具体的应用UP/DOWN网络接口,或修改接口双工速率等,所以在Vpp内部需要有对应的DPDK API可调用

VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (admin_up_down_function);
VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (link_up_down_function);

这两个宏定义向Vpp注册了两种类型接口的ADMIN和LINK UP/DOWN函数

#define VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION(f)		\
  _VNET_INTERFACE_FUNCTION_DECL(f,sw_interface_admin_up_down)

#define VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION(f)		\
  _VNET_INTERFACE_FUNCTION_DECL(f,hw_interface_link_up_down)

#define _VNET_INTERFACE_FUNCTION_DECL(f,tag)                            \
  _VNET_INTERFACE_FUNCTION_DECL_PRIO(f,tag,VNET_ITF_FUNC_PRIORITY_LOW)

以上通过VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION 和
VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION的封装sw_interface_admin_up_down和hw_interface_link_up_down标识把admin_up_down_function和link_up_down_function函数注册到vnm结构体中作为回调函数在需要的地方调用


#define _VNET_INTERFACE_FUNCTION_DECL_PRIO(f,tag,p)                    \
                                                                        \
static void __vnet_interface_function_init_##tag##_##f (void)           \
    __attribute__((__constructor__)) ;                                  \
                                                                        \
static void __vnet_interface_function_init_##tag##_##f (void)           \
{                                                                       \
 vnet_main_t * vnm = vnet_get_main();                                   \
 static _vnet_interface_function_list_elt_t init_function;              \
 init_function.next_interface_function = vnm->tag##_functions[p];       \
 vnm->tag##_functions[p] = &init_function;                              \
 init_function.fp = (void *) &f;                                        \
}                                                                       \
static void __vnet_interface_function_deinit_##tag##_##f (void)         \
    __attribute__((__destructor__)) ;                                   \
                                                                        \
static void __vnet_interface_function_deinit_##tag##_##f (void)         \
{                                                                       \
 vnet_main_t * vnm = vnet_get_main();                                   \
 _vnet_interface_function_list_elt_t *next;                             \
 if (vnm->tag##_functions[p]->fp == f)                                  \
    {                                                                   \
      vnm->tag##_functions[p] =                                         \
        vnm->tag##_functions[p]->next_interface_function;               \
      return;                                                           \
    }                                                                   \
  next = vnm->tag##_functions[p];                                       \
  while (next->next_interface_function)                                 \
    {                                                                   \
      if (next->next_interface_function->fp == f)                       \
        {                                                               \
          next->next_interface_function =                               \
            next->next_interface_function->next_interface_function;     \
          return;                                                       \
        }                                                               \
      next = next->next_interface_function;                             \
    }                                                                   \
}

如hw接口UP/DOWN处理函数vnet_hw_interface_set_flags_helper就会使用vnm的hw_interface_link_up_down_functions回调修改接口状态

static clib_error_t *
vnet_hw_interface_set_flags_helper (vnet_main_t * vnm, u32 hw_if_index,
				    vnet_hw_interface_flags_t flags,
				    vnet_interface_helper_flags_t
				    helper_flags)
{
  vnet_hw_interface_t *hi = vnet_get_hw_interface (vnm, hw_if_index);
  vnet_hw_interface_class_t *hw_class =
    vnet_get_hw_interface_class (vnm, hi->hw_class_index);
error = call_elf_section_interface_callbacks
	(vnm, hw_if_index, flags, vnm->hw_interface_link_up_down_functions);
....................
....................
....................
static clib_error_t *
call_elf_section_interface_callbacks (vnet_main_t * vnm, u32 if_index,
				      u32 flags,
				      _vnet_interface_function_list_elt_t **
				      elts)
{
  _vnet_interface_function_list_elt_t *elt;
  vnet_interface_function_priority_t prio;
  clib_error_t *error = 0;

  for (prio = VNET_ITF_FUNC_PRIORITY_LOW;
       prio <= VNET_ITF_FUNC_PRIORITY_HIGH; prio++)
    {
      elt = elts[prio];

      while (elt)
	{
	  error = elt->fp (vnm, if_index, flags);
	  if (error)
	    return error;
	  elt = elt->next_interface_function;
	}
    }
  return error;
}

你可能感兴趣的:(FD.IO,VPP)