VPP的feature機制


	早期的VPP本身的node框架比較固定,各個node之間邏輯連接已經固化。為此新版本增加了feature機制,每個feature是一個node,
用戶可以啟用/停止某個或某些feature。用戶也可以自己寫插件,把自定義node(自己的業務邏輯)加入到指定位置。
	feature機制在node機制的基礎上進行構建,通過改變下一個node的選擇實現動態變更業務拓撲,提高了靈活性。VPP的一個arc中的所有feature可以看成一個新的大的抽象的node。該arc中的node按照排好的序號運行,不按照node的指向運行。

feature機制注冊

feature arc注冊對象

/** feature registration object */
typedef struct _vnet_feature_arc_registration
{
  /** next registration in list of all registrations 該arc下面的所有的feature鏈表 */
  struct _vnet_feature_arc_registration *next;
  /** Feature Arc name */
  char *arc_name;/* arc的名字,arc可以僅僅是一個名字,不需要對應的node */
  /** Start nodes 該arc的起始節點以及個數,從該節點會進入arc處理 */
  char **start_nodes;
  int n_start_nodes;
  /** End of the arc (optional, for consistency-checking)該arc中的最后一個feature的名字,可選的,
   ** 用於校驗warshall算法排序后最后一個feature是否為指定的feature */
  char *last_in_arc;
  /* Feature arc index, assigned by init function 初始化函數分配的arc索引 */
  u8 feature_arc_index;
  u8 *arc_index_ptr; /* 初始化函數分配的arc索引*arc_index_ptr == feature_arc_index。兩者用的地方不一樣,用於解耦合 */
} vnet_feature_arc_registration_t;

feature注冊對象

/* Enable feature callback. 使能特性的回調函數,不同feature不一樣 */
typedef clib_error_t *(vnet_feature_enable_disable_function_t)
  (u32 sw_if_index, int enable_disable);

/** feature registration object */
typedef struct _vnet_feature_registration
{
  /** next registration in list of all registrations 兩個指針,next用於把所有feature鏈接到一個鏈表。
	  next_in_arc動態使用,將feature按照所屬的arc進行分類 */
  struct _vnet_feature_registration *next, *next_in_arc;
  /** Feature arc name 所屬arc的名字 */
  char *arc_name;
  /** Graph node name 該feature對應的node的名字,node必須存在 */
  char *node_name;
  /** Pointer to this feature index, filled in by vnet_feature_arc_init */
  u32 *feature_index_ptr;/* 初始化函數分配的feature索引*feature_index_ptr == feature_index */
  u32 feature_index;/* 兩者用的地方不一樣,主要用於解耦合 */
  /** Constraints of the form "this feature runs before X" */
  char **runs_before;/* 限制條件,表示在哪些feature之前運行,用於指導最后生成有序feature列表 */
  /** Constraints of the form "this feature runs after Y" */
  char **runs_after;/* 限制條件,表示在哪些feature之后運行,用於指導最后生成有序feature列表 */

  /** Function to enable/disable feature 使能特性的回調函數,
	  不同feature不一樣,主要用於特性的准備和清理工作。在特性使能和禁止時被調用
  **/
  vnet_feature_enable_disable_function_t *enable_disable_cb;
} vnet_feature_registration_t;

arc中feature限制條件注冊對象

/** constraint registration object,靈活的限制條件用於限制feature的執行順序,與前面的runs_before和runs_after聯合起來共同生效 */
typedef struct _vnet_feature_constraint_registration
{
  /** next constraint set in list of all registrations*/
  /** 形成鏈表next在注冊時初始化,所有同類鏈接到一個鏈表。
   ** next_in_arc動態使用,同一個arc的對象鏈接到一個鏈表 
   */
  struct _vnet_feature_constraint_registration *next, *next_in_arc;
  /** Feature arc name 所屬arc的名字 */
  char *arc_name;

  /** Feature arc index, assigned by init function 
   ** arc索引,從arc中獲取的,用於指向其所屬的arc
   */
  u8 feature_arc_index;

  /** Node names, to run in the specified order 
   ** 節點名字數組,是一個二維數組,數組下標越小優先級越高。
   ** 例如有數組node_names[2][2],那么順序為node_names[0][0]->node_names[0][1]->node_names[1][0]->node_names[1][1]
   ** 可以實現"a then b then c then d"更長的順序限制功能
   */
  char **node_names;
} vnet_feature_constraint_registration_t;

feature機制注冊管理全局對象

feature機制都是使用帶有__constructor__屬性的宏進行注冊的,所以需要一個全局變量來維護這些信息。這個全局變量就是:
extern vnet_feature_main_t feature_main;

typedef struct
{
  /** feature arc configuration list 所有注冊的arc連接成鏈表 */
  vnet_feature_arc_registration_t *next_arc;
  /* 所有注冊的arc根據名字構建成hash表,用於根據名字查找對應的vnet_feature_arc_registration_t */
  uword **arc_index_by_name;

  /** feature path configuration lists */
  vnet_feature_registration_t *next_feature;
  /** 鏈表數組,每一個arc一個鏈表,該鏈表包含了該arc所有的feature,在函數vnet_feature_init動態形成 */
  vnet_feature_registration_t **next_feature_by_arc;
  
  vnet_feature_constraint_registration_t *next_constraint;
  vnet_feature_constraint_registration_t **next_constraint_by_arc;
  /* hash表數組,以arc所有作為下標,每一個成員是一個hash表,構建該arc中的特性名字與特性之間的映射 */
  uword **next_feature_by_name;

  /** feature config main objects */
  /* 根據arc_index獲取對應的arc的配置項,一個arc一個元素 */
  vnet_feature_config_main_t *feature_config_mains;

  /** Save partial order results for show command */
  /** 以arc索引為下標,每一個成員是一個特性數組,數組包含了該arc的有序特性 */
  char ***feature_nodes;

  /** bitmap of interfaces which have driver rx features configured */
  /** 位域數組,二維數組,每一個arc作為一維索引,接口作為二維索引 */
  uword **sw_if_index_has_features;

  /** feature reference counts by interface */
  /** i16數組,表示接口上對應的arc上配置的特性的數目,是一個二維數組:第一維度是接口索引,第二維度是arc */
  i16 **feature_count_by_sw_if_index;

  /** Feature arc index for device-input */
  /** device input arc分配的索引 */
  u8 device_input_feature_arc_index;

  /** convenience */
  vlib_main_t *vlib_main;
  vnet_main_t *vnet_main;
} vnet_feature_main_t;

feature機制配置管理結構

typedef struct
{
  /* Pool of configs.  Index 0 is always null config and is never deleted. */
  vnet_config_t *config_pool;

  /* Hash table mapping vector config string to config pool index. */
  uword *config_string_hash;

  /* Global heap of configuration data. */
  u32 *config_string_heap;

  /* Node index which starts/ends feature processing. */
  /* 該arc的起始節點數組和結束節點的node索引 */
  u32 *start_node_indices, end_node_index;

  /* Interior feature processing nodes (not including start and end nodes). */
  /* 根據feature索引找到node索引的映射表 */
  u32 *node_index_by_feature_index;

  /* vnet_config pool index by user index */
  u32 *config_pool_index_by_user_index;

  /* Temporary vector for holding config strings.  Used to avoid continually
     allocating vectors. */
  u32 *config_string_temp;
} vnet_config_main_t;

typedef struct vnet_feature_config_main_t_
{
  vnet_config_main_t config_main;  /* arc的配置結構,主要與配置相關,后面分析配置章節會深入分析 */
  u32 *config_index_by_sw_if_index;/* 特性的接口配置索引,根據接口索引獲取該接口上配置的feature配置索引 */
} vnet_feature_config_main_t;

feature機制注冊宏定義

#ifndef CLIB_MARCH_VARIANT
#define VNET_FEATURE_ARC_INIT(x,...)				\             /* 注冊arc對象宏 */
  __VA_ARGS__ vnet_feature_arc_registration_t vnet_feat_arc_##x;\ /* 聲明一個vnet_feature_arc_registration_t對象 */
static void __vnet_add_feature_arc_registration_##x (void)	\     /* 聲明一個__vnet_add_feature_arc_registration_函數 */
  __attribute__((__constructor__)) ;				\
static void __vnet_add_feature_arc_registration_##x (void)	\     /* 定義__vnet_add_feature_arc_registration_函數 */
{								\
  vnet_feature_main_t * fm = &feature_main;			\
  vnet_feat_arc_##x.next = fm->next_arc;			\ /* 將靜態變量vnet_feat_arc_##x連接fm->next_arc到鏈表中 */
  fm->next_arc = & vnet_feat_arc_##x;				\
}								\
static void __vnet_rm_feature_arc_registration_##x (void)	\
  __attribute__((__destructor__)) ;				\
static void __vnet_rm_feature_arc_registration_##x (void)	\
{								\
  vnet_feature_main_t * fm = &feature_main;			\
  vnet_feature_arc_registration_t *r = &vnet_feat_arc_##x;	\
  VLIB_REMOVE_FROM_LINKED_LIST (fm->next_arc, r, next);		\
}								\
__VA_ARGS__ vnet_feature_arc_registration_t vnet_feat_arc_##x   /* 定義初始化vnet_feat_arc_##x變量 */

#define VNET_FEATURE_INIT(x,...)				\               /* 注冊feature對象宏 */
  __VA_ARGS__ vnet_feature_registration_t vnet_feat_##x;	\   /* 聲明一個vnet_feature_registration_t對象 */
static void __vnet_add_feature_registration_##x (void)		\   /* 聲明一個__vnet_add_feature_registration_##x_函數 */
  __attribute__((__constructor__)) ;				\
static void __vnet_add_feature_registration_##x (void)		\   /* 定義__vnet_add_feature_registration_##x函數 */
{								\
  vnet_feature_main_t * fm = &feature_main;			\
  vnet_feat_##x.next = fm->next_feature;			\           /* 將靜態變量vnet_feat_##x連接fm->next_feature到鏈表中 */
  fm->next_feature = & vnet_feat_##x;				\
}								\
static void __vnet_rm_feature_registration_##x (void)		\
  __attribute__((__destructor__)) ;				\
static void __vnet_rm_feature_registration_##x (void)		\
{								\
  vnet_feature_main_t * fm = &feature_main;			\
  vnet_feature_registration_t *r = &vnet_feat_##x;		\
  VLIB_REMOVE_FROM_LINKED_LIST (fm->next_feature, r, next);	\
}								\
__VA_ARGS__ vnet_feature_registration_t vnet_feat_##x          /* 定義初始化vnet_feat_##x變量 */

#define VNET_FEATURE_ARC_ORDER(x,...)                                   \  /* 該宏用的非常少,目前vpp還沒有使用過該宏 */
  __VA_ARGS__ vnet_feature_constraint_registration_t 			\
vnet_feature_constraint_##x;                                            \
static void __vnet_add_constraint_registration_##x (void)		\
  __attribute__((__constructor__)) ;                                    \
static void __vnet_add_constraint_registration_##x (void)		\
{                                                                       \
  vnet_feature_main_t * fm = &feature_main;                             \
  vnet_feature_constraint_##x.next = fm->next_constraint;    \ /* 將靜態變量vnet_feature_constraint_##x連接fm->next_constraint到鏈表中 */
  fm->next_constraint = & vnet_feature_constraint_##x;                  \
}                                                                       \
static void __vnet_rm_constraint_registration_##x (void)		\
  __attribute__((__destructor__)) ;                                     \
static void __vnet_rm_constraint_registration_##x (void)		\
{                                                                       \
  vnet_feature_main_t * fm = &feature_main;                             \
  vnet_feature_constraint_registration_t *r = &vnet_feature_constraint_##x; \
  VLIB_REMOVE_FROM_LINKED_LIST (fm->next_constraint, r, next);          \
}                                                                       \
__VA_ARGS__ vnet_feature_constraint_registration_t vnet_feature_constraint_##x  /* 定義初始化vnet_feature_constraint_##x變量 */

#else
#define VNET_FEATURE_ARC_INIT(x,...)				\
extern vnet_feature_arc_registration_t __clib_unused vnet_feat_arc_##x; \
static vnet_feature_arc_registration_t __clib_unused __clib_unused_vnet_feat_arc_##x
#define VNET_FEATURE_INIT(x,...)				\
extern vnet_feature_registration_t __clib_unused vnet_feat_##x; \
static vnet_feature_registration_t __clib_unused __clib_unused_vnet_feat_##x

#define VNET_FEATURE_ARC_ORDER(x,...)                           \
extern vnet_feature_constraint_registration_t                   \
__clib_unused vnet_feature_constraint_##x;                      \
static vnet_feature_constraint_registration_t __clib_unused 	\
__clib_unused_vnet_feature_constraint_##x
#endif	

feature機制注冊實例

我們以snat為例看一下宏定義的使用
/* Hook up input features */
VNET_FEATURE_INIT (ip4_snat_in2out, static) = {
  .arc_name = "ip4-unicast",
  .node_name = "nat44-in2out",  /* 必須存在nat44-in2out節點 */
  .runs_after = VNET_FEATURES ("acl-plugin-in-ip4-fa"),
};	

VNET_FEATURE_ARC_INIT (ip4_unicast, static) =
{
  .arc_name = "ip4-unicast",
  .start_nodes = VNET_FEATURES ("ip4-input", "ip4-input-no-checksum"),/* 起始節點,必須存在node */
  .last_in_arc = "ip4-lookup", /* 整個arc的最后一個feature必須是ip4-lookup,否則不能初始化成功 */
  .arc_index_ptr = &ip4_main.lookup_main.ucast_feature_arc_index,/* 需要將arc索引寫入到ucast_feature_arc_index中,供對應模塊使用 */
};

feature機制初始化

依靠__constructor__屬性注冊了相應的feature對象后,在main函數中需要對這些對象進行加工處理,對應的初始化函數為vnet_feature_init:

static clib_error_t *
vnet_feature_init (vlib_main_t * vm)
{
  vnet_feature_main_t *fm = &feature_main;/* 獲取feature的全局注冊信息 */
  vnet_feature_registration_t *freg;
  vnet_feature_arc_registration_t *areg;
  vnet_feature_constraint_registration_t *creg;
  u32 arc_index = 0;/* arc索引從0開始 */
  /* 創建一個字符串hash表,用於構建arc名字與vnet_feature_arc_registration_t之間的映射關系 */
  fm->arc_index_by_name = hash_create_string (0, sizeof (uword));
  areg = fm->next_arc;

  /* process feature arc registrations */
  /* 處理每一個注冊了的arc */
  while (areg)
    {
      char *s;
      int i = 0;
      areg->feature_arc_index = arc_index;/* 動態分配arc索引 */
      if (areg->arc_index_ptr)
	*areg->arc_index_ptr = arc_index;/* 設置該arc的索引 */
      /* 構建arc名字與arc的描述結構體指針的映射關系 */
      hash_set_mem (fm->arc_index_by_name, areg->arc_name,
		    pointer_to_uword (areg));

      /* process start nodes,計算起始節點的個數 */
      while ((s = areg->start_nodes[i]))
	{
	  i++;
	}
      areg->n_start_nodes = i;/* 起始節點的個數 */

      /* next */
      areg = areg->next;
      arc_index++;
    }
  /* 確保每一個向量都能夠容納arc_index - 1個元素 */
  vec_validate (fm->next_feature_by_arc, arc_index - 1);
  vec_validate (fm->feature_nodes, arc_index - 1);
  vec_validate (fm->feature_config_mains, arc_index - 1);
  vec_validate (fm->next_feature_by_name, arc_index - 1);
  vec_validate (fm->sw_if_index_has_features, arc_index - 1);
  vec_validate (fm->feature_count_by_sw_if_index, arc_index - 1);
  vec_validate (fm->next_constraint_by_arc, arc_index - 1);
  /* 遍歷每一個特性 */
  freg = fm->next_feature;/* 遍歷每一個特性,構建next_feature_by_arc鏈表數組 */
  while (freg)
    {
      vnet_feature_registration_t *next;
      /* 獲取特性對應的arc */
      uword *p = hash_get_mem (fm->arc_index_by_name, freg->arc_name);
      if (p == 0)
	{
	  /* Don't start vpp with broken features arcs */
	  clib_warning ("Unknown feature arc '%s'", freg->arc_name);
	  os_exit (1);
	}

      areg = uword_to_pointer (p[0], vnet_feature_arc_registration_t *);
      arc_index = areg->feature_arc_index;

      next = freg->next;
      /* 根據feature所屬的arc構建鏈表,即將同一個arc的feature連接到一個鏈表中
       * 鏈表頭為fm->next_feature_by_arc[arc_index]
       */
      freg->next_in_arc = fm->next_feature_by_arc[arc_index];
      fm->next_feature_by_arc[arc_index] = freg;

      /* next */
      freg = next;
    }

  /* Move bulk constraints to the constraint by arc lists */
  /* 構建constraint鏈表,鏈表頭為fm->next_constraint_by_arc[arc_index] */
  creg = fm->next_constraint;
  while (creg)
    {
      vnet_feature_constraint_registration_t *next;
      uword *p = hash_get_mem (fm->arc_index_by_name, creg->arc_name);
      if (p == 0)
	{
	  /* Don't start vpp with broken features arcs */
	  clib_warning ("Unknown feature arc '%s'", creg->arc_name);
	  os_exit (1);
	}

      areg = uword_to_pointer (p[0], vnet_feature_arc_registration_t *);
      arc_index = areg->feature_arc_index;

      next = creg->next;
      creg->next_in_arc = fm->next_constraint_by_arc[arc_index];
      fm->next_constraint_by_arc[arc_index] = creg;

      /* next */
      creg = next;
    }

  /* 再次遍歷每一個arc,調用vnet_feature_arc_init對該arc中的feature進行初始化 */
  areg = fm->next_arc;
  while (areg)
    {
      clib_error_t *error;
      vnet_feature_config_main_t *cm;
      vnet_config_main_t *vcm;
      char **features_in_order, *last_feature;

      arc_index = areg->feature_arc_index;
      cm = &fm->feature_config_mains[arc_index];
      vcm = &cm->config_main;/* 主要配置 */
      /* 初始化主要配置,主要是根據該arc中的feature的限制條件進行feature排序,校驗,構建順序 */
      if ((error = vnet_feature_arc_init
	   (vm, vcm, areg->start_nodes, areg->n_start_nodes,
	    fm->next_feature_by_arc[arc_index],/* 該節點的所有feature */
	    fm->next_constraint_by_arc[arc_index],/* 該節點的所有constraint */
	    &fm->feature_nodes[arc_index])))/* arc的節點數組 */
	{
	  clib_error_report (error);
	  os_exit (1);
	}
      /* 該arc已經排好序的feature數組 */
      features_in_order = fm->feature_nodes[arc_index];

      /* If specified, verify that the last node in the arc is actually last */
      /* 如果指定了需要校驗的排序,那么進行校驗 */
      if (areg->last_in_arc && vec_len (features_in_order) > 0)
	{
	  last_feature = features_in_order[vec_len (features_in_order) - 1];
      /* 比較最后一個特性的名字是否是我們指定的 */
	  if (strncmp (areg->last_in_arc, last_feature,
		       strlen (areg->last_in_arc)))
	    clib_warning
	      ("WARNING: %s arc: last node is %s, but expected %s!",
	       areg->arc_name, last_feature, areg->last_in_arc);
	}
      /* 為該arc創建一個特性名字hash表,構建特性名字與特性之間的映射關系 */
      fm->next_feature_by_name[arc_index] =
	hash_create_string (0, sizeof (uword));
      freg = fm->next_feature_by_arc[arc_index];

      while (freg)
	{
	  hash_set_mem (fm->next_feature_by_name[arc_index],
			freg->node_name, pointer_to_uword (freg));
	  freg = freg->next_in_arc;
	}

      /* next */
      areg = areg->next;
      arc_index++;
    }

  return 0;
}

單個arc初始化

使用函數vnet_feature_arc_init初始化一個arc中的feature

/**
 * @brief Initialize a feature graph arc
 * @param vm vlib main structure pointer
 * @param vcm vnet config main structure pointer
 * @param feature_start_nodes names of start-nodes which use this
 *	  feature graph arc
 * @param num_feature_start_nodes number of start-nodes
 * @param first_reg first element in
 *        [an __attribute__((constructor)) function built, or
 *        otherwise created] singly-linked list of feature registrations
 * @param first_const first element in
 *        [an __attribute__((constructor)) function built, or
 *        otherwise created] singly-linked list of bulk order constraints
 * @param [out] in_feature_nodes returned vector of
 *        topologically-sorted feature node names, for use in
 *        show commands
 * @returns 0 on success, otherwise an error message. Errors
 *        are fatal since they invariably involve mistyped node-names, or
 *        genuinely missing node-names
 */
clib_error_t *
vnet_feature_arc_init (vlib_main_t * vm,
		       vnet_config_main_t * vcm,
		       char **feature_start_nodes,                 /* 該arc的起始節點數組 */
		       int num_feature_start_nodes,                /* 起始節點數目,所謂起始節點即表示報文進入該節點后需要進行feature機制運行 */
		       vnet_feature_registration_t * first_reg,    /* 該arc的特性鏈表頭 */
		       vnet_feature_constraint_registration_t *    /* 該arc的限制鏈表頭 */
		       first_const_set, char ***in_feature_nodes)  /* 有序特性名字存儲數組 */
{
  uword *index_by_name;
  uword *reg_by_index;
  u8 **node_names = 0;
  u8 *node_name;
  char *prev_name;
  char **these_constraints;
  char *this_constraint_c;
  u8 **constraints = 0;
  u8 *constraint_tuple;
  u8 *this_constraint;
  u8 **orig, **closure;
  uword *p;
  int i, j, k;
  u8 *a_name, *b_name;
  int a_index, b_index;
  int n_features;
  u32 *result = 0;
  vnet_feature_registration_t *this_reg = 0;
  vnet_feature_constraint_registration_t *this_const_set = 0;
  char **feature_nodes = 0;
  hash_pair_t *hp;
  u8 **keys_to_delete = 0;

  index_by_name = hash_create_string (0, sizeof (uword));
  reg_by_index = hash_create (0, sizeof (uword));

  this_reg = first_reg;

  /* pass 1, collect feature node names, construct a before b pairs */
  /* 第一步:收集特性節點名字,構建一個a,b對數組 */
  while (this_reg)
    {
      node_name = format (0, "%s%c", this_reg->node_name, 0);/* 特性節點名字 */
      hash_set (reg_by_index, vec_len (node_names), (uword) this_reg);/* 特性索引與節點hash */
      /* 特性索引與名字hash */
      hash_set_mem (index_by_name, node_name, vec_len (node_names));
      /* 加入數組 */
      vec_add1 (node_names, node_name);
      /* 在節點之前進行執行,遍歷每一個這樣的節點 */
      these_constraints = this_reg->runs_before;
      while (these_constraints && these_constraints[0])
	{
	  this_constraint_c = these_constraints[0];

	  constraint_tuple = format (0, "%s,%s%c", node_name,
				     this_constraint_c, 0);
	  vec_add1 (constraints, constraint_tuple);
	  these_constraints++;
	}
      /* 在節點之后執行 */
      these_constraints = this_reg->runs_after;
      while (these_constraints && these_constraints[0])
	{
	  this_constraint_c = these_constraints[0];

	  constraint_tuple = format (0, "%s,%s%c",
				     this_constraint_c, node_name, 0);
	  vec_add1 (constraints, constraint_tuple);
	  these_constraints++;
	}

      this_reg = this_reg->next_in_arc;
    }

  /* pass 2, collect bulk "a then b then c then d" constraints */
  /* 處理額外的限制,限制是一個鏈表,鏈表的每一個節點時是一個節點數組,表示一種依賴關系,限制性前面的后執行后面的 */
  this_const_set = first_const_set;
  while (this_const_set)
    {
      these_constraints = this_const_set->node_names;

      prev_name = 0;
      /* Across the list of constraints */
      while (these_constraints && these_constraints[0])
	{
	  this_constraint_c = these_constraints[0];
      /* 獲取限制特性 */
	  p = hash_get_mem (index_by_name, this_constraint_c);
	  if (p == 0)
	    {
	      clib_warning
		("bulk constraint feature node '%s' not found for arc '%s'",
		 this_constraint_c);
	      these_constraints++;
	      continue;
	    }

	  if (prev_name == 0)
	    {
	      prev_name = this_constraint_c;
	      these_constraints++;
	      continue;
	    }

	  constraint_tuple = format (0, "%s,%s%c", prev_name,
				     this_constraint_c, 0);
	  vec_add1 (constraints, constraint_tuple);
	  prev_name = this_constraint_c;
	  these_constraints++;
	}

      this_const_set = this_const_set->next_in_arc;
    }
  /* 下面的代碼整個是使用warshall算法對有向圖傳遞閉包進行處理,目的是依據限制構建一個合理feature執行順序。有點燒腦,有空再研究 */
  n_features = vec_len (node_names);
  /*  構建一個n_features*n_features的矩陣 */
  orig = clib_ptclosure_alloc (n_features);
  /* 遍歷每一個限制條件 */
  /* 構建前后依賴矩陣 */
  for (i = 0; i < vec_len (constraints); i++)
    {
      this_constraint = constraints[i];

      if (comma_split (this_constraint, &a_name, &b_name))
	    return clib_error_return (0, "comma_split failed!");

      p = hash_get_mem (index_by_name, a_name);
      /*
       * Note: the next two errors mean that something is
       * b0rked. As in: if you code "A depends on B," and you forget
       * to define a FEATURE_INIT macro for B, you lose.
       * Nonexistent graph nodes are tolerated.
       */
      if (p == 0)
	{
	  clib_warning ("feature node '%s' not found (before '%s', arc '%s')",
			a_name, b_name, first_reg->arc_name);
	  continue;
	}
      a_index = p[0];

      p = hash_get_mem (index_by_name, b_name);
      if (p == 0)
	{
	  clib_warning ("feature node '%s' not found (after '%s', arc '%s')",
			b_name, a_name, first_reg->arc_name);
	  continue;
	}
      b_index = p[0];

      /* add a before b to the original set of constraints */
      orig[a_index][b_index] = 1;
      vec_free (this_constraint);
    }

  /* Compute the positive transitive closure of the original constraints */
  closure = clib_ptclosure (orig);

  /* Compute a partial order across feature nodes, if one exists. */
again:
  for (i = 0; i < n_features; i++)
    {
        for (j = 0; j < n_features; j++)
    	{
    	  if (closure[i][j])
    	    goto item_constrained;
    	}
        /* Item i can be output */
        vec_add1 (result, i);
        {
            for (k = 0; k < n_features; k++)
              closure[k][i] = 0;
            /*
             * Add a "Magic" a before a constraint.
             * This means we'll never output it again
             */
            closure[i][i] = 1;
            goto again;
        }
    item_constrained:
      ;
    }

  /* see if we got a partial order... */
  if (vec_len (result) != n_features)
    return clib_error_return
      (0, "Arc '%s': failed to find a suitable feature order!",
       first_reg->arc_name);

  /*
   * We win.
   * Bind the index variables, and output the feature node name vector
   * using the partial order we just computed. Result is in stack
   * order, because the entry with the fewest constraints (e.g. none)
   * is output first, etc.
   */

  for (i = n_features - 1; i >= 0; i--)
    {
      p = hash_get (reg_by_index, result[i]);
      ASSERT (p != 0);
      this_reg = (vnet_feature_registration_t *) p[0];
      if (this_reg->feature_index_ptr)
	*this_reg->feature_index_ptr = n_features - (i + 1);
      this_reg->feature_index = n_features - (i + 1);
      vec_add1 (feature_nodes, this_reg->node_name);
    }

  /* Set up the config infrastructure */
  /* 構建feature與node索引之間的映射關系,同時設置起始節點和終止節點 */
  vnet_config_init (vm, vcm,
		    feature_start_nodes,
		    num_feature_start_nodes,
		    feature_nodes, vec_len (feature_nodes));

  /* Save a copy for show command */
  *in_feature_nodes = feature_nodes;

  /* Finally, clean up all the shit we allocated */
  /* *INDENT-OFF* */
  hash_foreach_pair (hp, index_by_name,
  ({
    vec_add1 (keys_to_delete, (u8 *)hp->key);
  }));
  /* *INDENT-ON* */
  hash_free (index_by_name);
  for (i = 0; i < vec_len (keys_to_delete); i++)
    vec_free (keys_to_delete[i]);
  vec_free (keys_to_delete);
  hash_free (reg_by_index);
  vec_free (result);
  clib_ptclosure_free (orig);
  clib_ptclosure_free (closure);
  return 0;
}

構建feature與對應node的關聯關系

使用函數vnet_config_init進行構建

void
vnet_config_init (vlib_main_t * vm,
		  vnet_config_main_t * cm,
		  char *start_node_names[],/* 該arc的起始節點和個數 */
		  int n_start_node_names,
		  char *feature_node_names[], int n_feature_node_names)/* 該arc的特性節點和個數 */
{
  vlib_node_t *n;
  u32 i;

  clib_memset (cm, 0, sizeof (cm[0]));
  /* 創建一個hash表 */
  cm->config_string_hash =
    hash_create_vec (0,
		     STRUCT_SIZE_OF (vnet_config_t, config_string_vector[0]),
		     sizeof (uword));

  ASSERT (n_feature_node_names >= 1);
  /* 遍歷每一個起始節點,根據起始節點名字獲取對應節點的索引,構建starts索引與節點索引之間的映射 */
  vec_resize (cm->start_node_indices, n_start_node_names);
  for (i = 0; i < n_start_node_names; i++)
    {
      n = vlib_get_node_by_name (vm, (u8 *) start_node_names[i]);
      /* Given node name must exist. */
      ASSERT (n != 0);
      cm->start_node_indices[i] = n->index;
    }
  /* 遍歷所有featrue節點,構建feature索引與節點索引之間的映射 */
  vec_resize (cm->node_index_by_feature_index, n_feature_node_names);
  for (i = 0; i < n_feature_node_names; i++)
    {
      if (!feature_node_names[i])
	cm->node_index_by_feature_index[i] = ~0;
      else
	{
	  n = vlib_get_node_by_name (vm, (u8 *) feature_node_names[i]);
	  /* Given node may exist in plug-in library which is not present */
	  if (n)
	    {
	      if (i + 1 == n_feature_node_names)/* 最后一個節點為我們的結束節點 */
		cm->end_node_index = n->index;
	      cm->node_index_by_feature_index[i] = n->index;
	    }
	  else
	    cm->node_index_by_feature_index[i] = ~0;
	}
    }
}

vnet_feature_init函數調用入口

該函數使用宏VLIB_INIT_FUNCTION (vnet_feature_init);進行初始化函數注冊,在三個地方會調用該函數:
1. ethernet_init
2. ip4_lookup_init
3. ip6_lookup_init
由vpp的初始化函數調用機制保證該函數只調用一次。

feature機制配置

前面介紹的只是feature機制的注冊過程,並沒有生效。只有配置后才能生效。

配置實例分析

在分析配置之前,我們先看一下實際的配置過程中feature相關表項的現象。
我們以nat為例:

1. 給接口配置IP之前

vpp# show interface features GigabitEthernet3/0/0	
ip4-unicast:
	ip4-not-enabled

2. 配置nat之前,配置接口地址,查看接口的feature

vpp# set interface ip address GigabitEthernet3/0/0 192.168.134.128/24
vpp# show interface features GigabitEthernet3/0/0  
Feature paths configured on GigabitEthernet3/0/0...

......                                                                                                                                    

ip4-multicast:

ip4-unicast:
......

3. 配置nat之后,查看接口的feature

vpp# set interface nat44 in GigabitEthernet13/0/0 out GigabitEthernet3/0/0            
vpp# show interface features GigabitEthernet3/0/0   
Feature paths configured on GigabitEthernet3/0/0...

......

ip4-unicast:
  nat44-out2in-worker-handoff

......

從上面的配置變化可知,feature與接口相關,通過命令配置可以讓指定的feature生效。

4. gdb跟蹤feature調用過程

(gdb) b vnet_feature_enable_disable
Breakpoint 1 at 0x7fd5d5470d90: file /home/jd/vpp/src/vnet/feature/feature.c, line 277.
(gdb) c
Continuing.

Thread 1 "vpp_main" hit Breakpoint 1, vnet_feature_enable_disable (arc_name=arc_name@entry=0x7fd58e836c22 "ip4-unicast", 
 node_name=node_name@entry=0x7fd58e836bb8 "nat44-in2out-worker-handoff", sw_if_index=sw_if_index@entry=3, enable_disable=enable_disable@entry=1, 
 feature_config=feature_config@entry=0x0, n_feature_config_bytes=n_feature_config_bytes@entry=0) at /home/jd/vpp/src/vnet/feature/feature.c:277
277     {
(gdb) bt
#0  vnet_feature_enable_disable (arc_name=arc_name@entry=0x7fd58e836c22 "ip4-unicast", 
    node_name=node_name@entry=0x7fd58e836bb8"nat44-in2out-worker-handoff",sw_if_index=sw_if_index@entry=3,enable_disable=enable_disable@entry=1, 
    feature_config=feature_config@entry=0x0,n_feature_config_bytes=n_feature_config_bytes@entry=0)at/home/jd/vpp/src/vnet/feature/feature.c:277
#1  0x00007fd58e7915d0 in snat_interface_add_del (sw_if_index=3, is_inside=is_inside@entry=1 '\001', is_del=is_del@entry=0)
    at /home/jd/vpp/src/plugins/nat/nat.c:1935
#2  0x00007fd58e7fd8ef in snat_feature_command_fn (vm=<optimized out>, input=<optimized out>, cmd=<optimized out>)
    at /home/jd/vpp/src/plugins/nat/nat44_cli.c:698
#3  0x00007fd5d4725a46 in vlib_cli_dispatch_sub_commands (vm=vm@entry=0x7fd5d499f700 <vlib_global_main>, 
    cm=cm@entry=0x7fd5d499f900 <vlib_global_main+512>, input=input@entry=0x7fd5944a4f60, parent_command_index=<optimized out>)
    at /home/jd/vpp/src/vlib/cli.c:607
#4  0x00007fd5d4726087 in vlib_cli_dispatch_sub_commands (vm=vm@entry=0x7fd5d499f700 <vlib_global_main>, 
    cm=cm@entry=0x7fd5d499f900 <vlib_global_main+512>, input=input@entry=0x7fd5944a4f60, parent_command_index=<optimized out>)
    at /home/jd/vpp/src/vlib/cli.c:568
#5  0x00007fd5d4726087 in vlib_cli_dispatch_sub_commands (vm=vm@entry=0x7fd5d499f700 <vlib_global_main>, 
    cm=cm@entry=0x7fd5d499f900 <vlib_global_main+512>, input=input@entry=0x7fd5944a4f60, parent_command_index=parent_command_index@entry=0)
    at /home/jd/vpp/src/vlib/cli.c:568
#6  0x00007fd5d4726354 in vlib_cli_input (vm=0x7fd5d499f700 <vlib_global_main>, input=input@entry=0x7fd5944a4f60, 
    function=function@entry=0x7fd5d477f8a0 <unix_vlib_cli_output>, function_arg=function_arg@entry=0) at /home/jd/vpp/src/vlib/cli.c:707
#7  0x00007fd5d4781466 in unix_cli_process_input (cm=0x7fd5d49a0040 <unix_cli_main>, cli_file_index=0) at /home/jd/vpp/src/vlib/unix/cli.c:2420
#8  unix_cli_process (vm=0x7fd5d499f700 <vlib_global_main>, rt=0x7fd594494000, f=<optimized out>) at /home/jd/vpp/src/vlib/unix/cli.c:2536
#9  0x00007fd5d473dda6 in vlib_process_bootstrap (_a=<optimized out>) at /home/jd/vpp/src/vlib/main.c:1469
#10 0x00007fd5d423da44 in clib_calljmp () from /usr/lib/x86_64-linux-gnu/libvppinfra.so.19.08
#11 0x00007fd5937ffb80 in ?? ()
#12 0x00007fd5d4743911 in vlib_process_startup(f=0x0,p=0x7fd594494000,vm=0x7fd5d499f700<vlib_global_main>) at /home/jd/vpp/src/vlib/main.c:1491
#13 dispatch_process (vm=0x7fd5d499f700 <vlib_global_main>, p=0x7fd594494000, last_time_stamp=0, f=0x0) at /home/jd/vpp/src/vlib/main.c:1536
#14 0x0000000000000000 in ?? ()
(gdb) 

feature機制配置全局管理對象

typedef struct
{
  /* Pool of configs.  Index 0 is always null config and is never deleted. */
  /* 配置池,與config_string_heap存在相互指向過程,為什么分為兩塊內存保存配置,比較奇怪 */
  vnet_config_t *config_pool;

  /* Hash table mapping vector config string to config pool index. */
  /*  */
  uword *config_string_hash;

  /* Global heap of configuration data. */
  /* 全局配置數據堆,從堆中分配用於保存配置的內存 */
  u32 *config_string_heap;

  /* Node index which starts/ends feature processing. */
  /* 該arc的起始節點數組和結束節點的node索引 */
  u32 *start_node_indices, end_node_index;

  /* Interior feature processing nodes (not including start and end nodes). */
  /* 根據feature索引找到node索引的映射表 */
  u32 *node_index_by_feature_index;

  /* vnet_config pool index by user index */
  /* 根據用戶索引獲取配置池索引 */
  u32 *config_pool_index_by_user_index;

  /* Temporary vector for holding config strings.  Used to avoid continually
     allocating vectors. */
  u32 *config_string_temp;
} vnet_config_main_t;

typedef struct
{
  /* Sorted vector of features for this configuration. */
  /* 該配置中的有序特性數組 */
  vnet_config_feature_t *features;

  /* Config string as vector for hashing. */
  u32 *config_string_vector;

  /* Config string including all next indices and feature data as a vector. */
  /* 從堆中分配的一塊內存的索引config_string_heap_index,用於獲取對應的內存
     config_string_heap_handle用於銷毀該內存*/
  u32 config_string_heap_index, config_string_heap_handle;

  /* Index in main pool. */
  /* 主配置池中的索引 */
  u32 index;

  /* Number of interfaces/traffic classes that reference this config. 
   * 引用計數
   */
  u32 reference_count;
} vnet_config_t;

typedef struct
{
  /* Features are prioritized by index.  Smaller indices get
     performed first. 
     特性索引,有優先級的意義,越小優先級越高
     */
  u32 feature_index;

  /* VLIB node which performs feature. 特性對應的節點*/
  u32 node_index;

  /* Next index relative to previous node or main node. 相對於前一個節點的序號 */
  u32 next_index;

  /* Opaque per feature configuration data. 特性的透明數據 */
  u32 *feature_config;
} vnet_config_feature_t;

配置函數vnet_feature_enable_disable

int
vnet_feature_enable_disable (const char *arc_name, const char *node_name,
			     u32 sw_if_index, int enable_disable,
			     void *feature_config, u32 n_feature_config_bytes)
{
  u32 feature_index;
  u8 arc_index;
  /* 根據名字獲取其對應的arc的索引 */
  arc_index = vnet_get_feature_arc_index (arc_name);

  if (arc_index == (u8) ~ 0)
    return VNET_API_ERROR_INVALID_VALUE;
  /* 根據節點名字和arc_index獲取對應的特性索引 */
  feature_index = vnet_get_feature_index (arc_index, node_name);

  return vnet_feature_enable_disable_with_index (arc_index, feature_index,
						 sw_if_index, enable_disable,
						 feature_config,
						 n_feature_config_bytes);
}

vnet_feature_enable_disable_with_index

int
vnet_feature_enable_disable_with_index (u8 arc_index, u32 feature_index,/* arc索引,feature索引 */
					u32 sw_if_index, int enable_disable,/* 接口索引,使能/去使能 */
					void *feature_config,  /* 配置透明參數 */
					u32 n_feature_config_bytes)/* 配置透明參數大小 */
{
  vnet_feature_main_t *fm = &feature_main;
  vnet_feature_config_main_t *cm;
  i16 feature_count;
  u32 ci;

  if (arc_index == (u8) ~ 0)
    return VNET_API_ERROR_INVALID_VALUE;

  if (feature_index == ~0)
    return VNET_API_ERROR_INVALID_VALUE_2;
  /* 獲取arc對應的配置控制塊 */
  cm = &fm->feature_config_mains[arc_index];
  vec_validate_init_empty (cm->config_index_by_sw_if_index, sw_if_index, ~0);
  ci = cm->config_index_by_sw_if_index[sw_if_index];/* 獲取接口對應的配置索引 */

  vec_validate (fm->feature_count_by_sw_if_index[arc_index], sw_if_index);
  /* 獲取該接口上引用該arc的特性個數 */
  feature_count = fm->feature_count_by_sw_if_index[arc_index][sw_if_index];
  /* 如果是禁止,並且沒有使能過特性,那么直接返回 */
  if (!enable_disable && feature_count < 1)
    return 0;
  /* 如果是使能,那么調用vnet_config_add_feature,否則調用vnet_config_del_feature */
  ci = (enable_disable
	? vnet_config_add_feature
	: vnet_config_del_feature)
    (vlib_get_main (), &cm->config_main, ci, feature_index, feature_config,
     n_feature_config_bytes);
  if (ci == ~0)
    {
      return 0;
    }
  /* 重新設置配置索引 */
  cm->config_index_by_sw_if_index[sw_if_index] = ci;

  /* update feature count 更新統計信息*/
  enable_disable = (enable_disable > 0);
  feature_count += enable_disable ? 1 : -1;
  ASSERT (feature_count >= 0);
  /* 更新接口的特性bit位 */
  fm->sw_if_index_has_features[arc_index] =
    clib_bitmap_set (fm->sw_if_index_has_features[arc_index], sw_if_index,
		     (feature_count > 0));
  /* 跟新該接口的鄰居信息,暫不分析 */
  adj_feature_update (sw_if_index, arc_index, (feature_count > 0));
  /* 更新該接口上使能的該接口的特性個數 */
  fm->feature_count_by_sw_if_index[arc_index][sw_if_index] = feature_count;
  return 0;
}

vnet_config_add_feature

u32
vnet_config_add_feature (vlib_main_t * vm,
			 vnet_config_main_t * cm,
			 u32 config_string_heap_index,
			 u32 feature_index,
			 void *feature_config, u32 n_feature_config_bytes)
{
  vnet_config_t *old, *new;
  vnet_config_feature_t *new_features, *f;
  u32 n_feature_config_u32s;
  /* 根據特性索引獲取特性對應的node */
  u32 node_index = vec_elt (cm->node_index_by_feature_index, feature_index);

  if (node_index == ~0)		// feature node does not exist
    return ~0;
  /* 該特性是否已經配置過 */
  if (config_string_heap_index == ~0)/* 沒有配置過 */
    {
      old = 0;
      new_features = 0;
    }
  else/* 已經配置過,則獲取其配置控制塊 */
    {
      /* 根據堆索引獲取配置信息 */  
      u32 *p = vnet_get_config_heap (cm, config_string_heap_index);
      /* -1個偏移保存的是vnet_config_t信息地址 */
      old = pool_elt_at_index (cm->config_pool, p[-1]);
      new_features = old->features;
      if (new_features)
        /* 拷貝一份特性數組 */
	    new_features = duplicate_feature_vector (new_features);
    }
  /* 增加一個特性 */
  vec_add2 (new_features, f, 1);
  f->feature_index = feature_index;
  f->node_index = node_index;
  /* 計算新的配置信息有多個個四字節 */
  n_feature_config_u32s =
    round_pow2 (n_feature_config_bytes,
		sizeof (f->feature_config[0])) /
    sizeof (f->feature_config[0]);
  /* 將特性參數拷貝到f->feature_config中 */
  vec_add (f->feature_config, feature_config, n_feature_config_u32s);

  /* Sort (prioritize) features. */
  /* 對特性根據索引進行排序 */
  if (vec_len (new_features) > 1)
    vec_sort_with_function (new_features, feature_cmp);
  
  /* 釋放原來的配置 */
  if (old)
    remove_reference (cm, old);
  /* 構建一個新的vnet_config_t,增加引用計數 */
  new = find_config_with_features (vm, cm, new_features);
  new->reference_count += 1;

  /*
   * User gets pointer to config string first element
   * (which defines the pool index
   * this config string comes from).
   */
  vec_validate (cm->config_pool_index_by_user_index,
		new->config_string_heap_index + 1);
  /* 通過用戶索引獲取對應的配置索引 */
  cm->config_pool_index_by_user_index[new->config_string_heap_index + 1]
    = new - cm->config_pool;
  /* 返回用戶索引 */
  return new->config_string_heap_index + 1;
}

find_config_with_features

static vnet_config_t *
find_config_with_features (vlib_main_t * vm,
			   vnet_config_main_t * cm,
			   vnet_config_feature_t * feature_vector)
{
  u32 last_node_index = ~0;
  vnet_config_feature_t *f;
  u32 *config_string;
  uword *p;
  vnet_config_t *c;

  config_string = cm->config_string_temp;
  cm->config_string_temp = 0;
  if (config_string)
    _vec_len (config_string) = 0;

  /* 遍歷每一個特性數組 */
  vec_foreach (f, feature_vector)
  {
    /* Connect node graph. 將節點添加到全局的動態節點圖中,返回的值為當前節點在其前一個節點的下一個節點數組中的索引 */
    f->next_index = add_next (vm, cm, last_node_index, f->node_index);
    last_node_index = f->node_index;

    /* Store next index in config string. 存儲索引在配置字符中 */
    vec_add1 (config_string, f->next_index);

    /* Store feature config. 存儲配置參數在后面 */
    vec_add (config_string, f->feature_config, vec_len (f->feature_config));
  }

  /* Terminate config string with next for end node. */
  if (last_node_index == ~0 || last_node_index != cm->end_node_index)
    {
      u32 next_index = add_next (vm, cm, last_node_index, cm->end_node_index);
      vec_add1 (config_string, next_index);
    }

  /* See if config string is unique.config_string中是特性的索引 */
  p = hash_get_mem (cm->config_string_hash, config_string);
  if (p)
    {
      /* Not unique.  Share existing config. */
      cm->config_string_temp = config_string;	/* we'll use it again later. */
      free_feature_vector (feature_vector);
      /* 獲取配置索引 */
      c = pool_elt_at_index (cm->config_pool, p[0]);
    }
  else
    {
      u32 *d;
      /* config_pool中分配一個vnet_config_t結構 */
      pool_get (cm->config_pool, c);
      /* 計算vnet_config_t的索引 */
      c->index = c - cm->config_pool;
      /* 指向配置的特性數組 */
      c->features = feature_vector;
      c->config_string_vector = config_string;

      /* Allocate copy of config string in heap.
         VLIB buffers will maintain pointers to heap as they read out
         configuration data. */
      c->config_string_heap_index
	= heap_alloc (cm->config_string_heap, vec_len (config_string) + 1,
		      c->config_string_heap_handle);

      /* First element in heap points back to pool index. */
      d =
	vec_elt_at_index (cm->config_string_heap,
			  c->config_string_heap_index);
      d[0] = c->index;
      clib_memcpy (d + 1, config_string, vec_bytes (config_string));
      /* 構建配置字符向量地址與配置索引的映射關系 */
      hash_set_mem (cm->config_string_hash, config_string, c->index);

      c->reference_count = 0;	/* will be incremented by caller. */
    }

  return c;
}

vnet_config_del_feature

u32
vnet_config_del_feature (vlib_main_t * vm,
			 vnet_config_main_t * cm,
			 u32 config_string_heap_index,
			 u32 feature_index,
			 void *feature_config, u32 n_feature_config_bytes)
{
  vnet_config_t *old, *new;
  vnet_config_feature_t *new_features, *f;
  u32 n_feature_config_u32s;

  {
    /* 根據堆索引獲取配置堆 */
    u32 *p = vnet_get_config_heap (cm, config_string_heap_index);
    /* 獲取vnet_config_t配置 */
    old = pool_elt_at_index (cm->config_pool, p[-1]);
  }
  /* 計算該特性的私有透明數據長度 */
  n_feature_config_u32s =
    round_pow2 (n_feature_config_bytes,
		sizeof (f->feature_config[0])) /
    sizeof (f->feature_config[0]);

  /* Find feature with same index and opaque data. */
  /* 根據索引找到對應的featue,並且要求其對應的透明數據需要完全一樣 */
  vec_foreach (f, old->features)
  {
    if (f->feature_index == feature_index /* 遍歷每一個特性,比較特性索引以及私有透明數據 */
	&& vec_len (f->feature_config) == n_feature_config_u32s
	&& (n_feature_config_u32s == 0
	    || !memcmp (f->feature_config, feature_config,
			n_feature_config_bytes)))
      break;
  }

  /* Feature not found. 沒有找到*/
  if (f >= vec_end (old->features))
    return ~0;
  /* 復制特性結構 */
  new_features = duplicate_feature_vector (old->features);
  f = new_features + (f - old->features);/* 得到要刪除的特性的地址 */
  vnet_config_feature_free (f);
  /* 從數組中刪除該特性 */
  vec_delete (new_features, 1, f - new_features);

  /* must remove old from config_pool now as it may be expanded and change
     memory location if the following function find_config_with_features()
     adds a new config because none of existing config's has matching features
     and so can be reused */
  remove_reference (cm, old);
  /* 將新特性重新插入 */
  new = find_config_with_features (vm, cm, new_features);
  new->reference_count += 1;

  vec_validate (cm->config_pool_index_by_user_index,
		new->config_string_heap_index + 1);
  cm->config_pool_index_by_user_index[new->config_string_heap_index + 1]
    = new - cm->config_pool;
  return new->config_string_heap_index + 1;
}

到此,整個配置的添加刪除介紹完畢,似乎在刪除feature函數中沒有add_next的反操作,是否為一個bug?

feature機制運行流程

前面的章節中,我們分析了feature機制的配置流程。配置號feature機制后,報文在運行過程中將會經過這些動態添加
的節點。

feature arc的起始節點

我們以前面提到的snat注冊的節點為例進行分析
VNET_FEATURE_ARC_INIT (ip4_unicast, static) =
{
  .arc_name = "ip4-unicast",
  .start_nodes = VNET_FEATURES ("ip4-input", "ip4-input-no-checksum"),/* 起始節點,必須存在node */
  .last_in_arc = "ip4-lookup", /* 整個arc的最后一個feature必須是ip4-lookup,否則不能初始化成功 */
  .arc_index_ptr = &ip4_main.lookup_main.ucast_feature_arc_index,/* 需要將arc索引寫入到ucast_feature_arc_index中,供對應模塊使用 */
};
起始節點為"ip4-input", "ip4-input-no-checksum"兩個節點。兩個節點的功能函數都會調用ip4_input_inline函數。

ip4_input_set_next

p4_input_inline對報文處理完節點的功能后,會調用函數ip4_input_set_next進行下一個節點的選擇,我們重點分析一下函數ip4_input_set_next:

static_always_inline u32
ip4_input_set_next (u32 sw_if_index, vlib_buffer_t * b, int arc_enabled)/* 參數分別為接口索引,報文buf,是否使能arc機制 */
{
  ip4_main_t *im = &ip4_main;
  ip_lookup_main_t *lm = &im->lookup_main;
  u32 next;
  u8 arc;

  ip4_header_t *ip = vlib_buffer_get_current (b);

  if (PREDICT_FALSE (ip4_address_is_multicast (&ip->dst_address)))/* 組播報文,默認的下一個節點為IP4_INPUT_NEXT_LOOKUP_MULTICAST */
    {
      next = IP4_INPUT_NEXT_LOOKUP_MULTICAST;
      arc = lm->mcast_feature_arc_index;/* 這個是在feature在初始化設置的,即arc_index_ptr參數 */
    }
  else
    {
      next = IP4_INPUT_NEXT_LOOKUP;/* 單播報文默認的下一個節點為IP4_INPUT_NEXT_LOOKUP */
      arc = lm->ucast_feature_arc_index;/* 獲取ucast_feature索引,這個是在feature在初始化設置的,即arc_index_ptr參數 */
    }

  if (arc_enabled)/* 使能了arc機制,進行下一個節點選擇 */
    vnet_feature_arc_start (arc, sw_if_index, &next, b);

  return next;
}

vnet_feature_arc_start

static_always_inline void
vnet_feature_arc_start (u8 arc, u32 sw_if_index, u32 * next0,
			vlib_buffer_t * b0)
{
  vnet_feature_arc_start_with_data (arc, sw_if_index, next0, b0, 0);
}

static_always_inline void *
vnet_feature_arc_start_with_data (u8 arc, u32 sw_if_index, u32 * next,/* 參數為arc索引,接口索引,得到的下一個node索引 */
				  vlib_buffer_t * b, u32 n_data_bytes)
{
  vnet_feature_main_t *fm = &feature_main;
  vnet_feature_config_main_t *cm;
  cm = &fm->feature_config_mains[arc];

  if (PREDICT_FALSE (vnet_have_features (arc, sw_if_index)))/* 該接口是否使能了該arc */
    {
      vnet_buffer (b)->feature_arc_index = arc;
      b->current_config_index =/* 根據接口索引獲取對應的配置索引 */
	vec_elt (cm->config_index_by_sw_if_index, sw_if_index);
      return vnet_get_config_data (&cm->config_main, &b->current_config_index,
				   next, n_data_bytes);
    }
  return 0;
}

always_inline void *
vnet_get_config_data (vnet_config_main_t * cm,
		      u32 * config_index, u32 * next_index, u32 n_data_bytes)
{
  u32 i, n, *d;

  i = *config_index;
  /* 從堆中根據配置索引獲取配置起始地址 */
  d = heap_elt_at_index (cm->config_string_heap, i);
  /* n_data_bytes傳入的是0,所以n等於0 */
  n = round_pow2 (n_data_bytes, sizeof (d[0])) / sizeof (d[0]);

  /* Last 32 bits are next index. */
  /* 該內存的-1下標存儲的是config索引,d[n]=d[0]然后是第一個下一個節點的索引 */
  *next_index = d[n];

  /* Advance config index to next config. */
  /* 沒看明白,這是干啥,估計與堆的實現有關,先不管,后續分析 */
  *config_index = (i + n + 1);

  /* Return config data to user for this feature. */
  return (void *) d;
}

起始節點通過vnet_feature_arc_start函數即可改變原來的下一個節點的流程,切入feature框架

feature節點如何運行下一個feature節點

我們以snat為例進行分析
/* Hook up ip4-local features */
VNET_FEATURE_INIT (ip4_nat_hairpinning, static) =
{
  .arc_name = "ip4-local",
  .node_name = "nat44-hairpinning",
  .runs_before = VNET_FEATURES("ip4-local-end-of-arc"),
};
對應的節點和功能函數為
VLIB_NODE_FN (nat44_hairpinning_node) (vlib_main_t * vm,
			       vlib_node_runtime_t * node,
			       vlib_frame_t * frame)
{
  return nat44_hairpinning_fn_inline (vm, node, frame, 0);
}

/* *INDENT-OFF* */
VLIB_REGISTER_NODE (nat44_hairpinning_node) = {
  .name = "nat44-hairpinning",
  .vector_size = sizeof (u32),
  .type = VLIB_NODE_TYPE_INTERNAL,
  .n_errors = ARRAY_LEN(nat44_hairpin_error_strings),
  .error_strings = nat44_hairpin_error_strings,
  .n_next_nodes = NAT_HAIRPIN_N_NEXT,
  .next_nodes = {
    [NAT_HAIRPIN_NEXT_DROP] = "error-drop",
    [NAT_HAIRPIN_NEXT_LOOKUP] = "ip4-lookup",
  },
};
/* *INDENT-ON* */

nat44_hairpinning_fn_inline

static inline uword
nat44_hairpinning_fn_inline (vlib_main_t * vm,
			     vlib_node_runtime_t * node,
			     vlib_frame_t * frame, int is_ed)
{
  u32 n_left_from, *from, *to_next, stats_node_index;
  nat_hairpin_next_t next_index;
  u32 pkts_processed = 0;
  snat_main_t *sm = &snat_main;
  vnet_feature_main_t *fm = &feature_main;
  u8 arc_index = vnet_feat_arc_ip4_local.feature_arc_index;
  vnet_feature_config_main_t *cm = &fm->feature_config_mains[arc_index];

  stats_node_index = is_ed ? sm->ed_hairpinning_node_index :
    sm->hairpinning_node_index;
  from = vlib_frame_vector_args (frame);
  n_left_from = frame->n_vectors;
  next_index = node->cached_next_index;

  while (n_left_from > 0)
    {
      u32 n_left_to_next;

      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);

      while (n_left_from > 0 && n_left_to_next > 0)
	{
	  u32 bi0;
	  vlib_buffer_t *b0;
	  u32 next0;
	  ip4_header_t *ip0;
	  u32 proto0;
	  udp_header_t *udp0;
	  tcp_header_t *tcp0;

	  /* speculatively enqueue b0 to the current next frame */
	  bi0 = from[0];
	  to_next[0] = bi0;
	  from += 1;
	  to_next += 1;
	  n_left_from -= 1;
	  n_left_to_next -= 1;

	  b0 = vlib_get_buffer (vm, bi0);
	  ip0 = vlib_buffer_get_current (b0);
	  udp0 = ip4_next_header (ip0);
	  tcp0 = (tcp_header_t *) udp0;

	  proto0 = ip_proto_to_snat_proto (ip0->protocol);

	  vnet_get_config_data (&cm->config_main, &b0->current_config_index,
				&next0, 0);/* 直接使用該函數選擇下一個feature節點,這個函數在上面已經分析過了 */

	  if (snat_hairpinning (sm, b0, ip0, udp0, tcp0, proto0, is_ed))
	    next0 = NAT_HAIRPIN_NEXT_LOOKUP;

	  pkts_processed += next0 != NAT_HAIRPIN_NEXT_DROP;

	  /* verify speculative enqueue, maybe switch current next frame */
	  vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
					   to_next, n_left_to_next,
					   bi0, next0);
	}

      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
    }

  vlib_node_increment_counter (vm, stats_node_index,
			       NAT44_HAIRPIN_ERROR_PROCESSED, pkts_processed);
  return frame->n_vectors;
}

小結

對於feature節點可以調用如下函數進行下一個節點的選取,feature機制比較靈活,不是所有feature節點都會調用下面的函數,
機制比較復雜,后續繼續分析

static_always_inline void *
vnet_feature_next_with_data (u32 * next0, vlib_buffer_t * b0,
			     u32 n_data_bytes)
{
  vnet_feature_main_t *fm = &feature_main;
  u8 arc = vnet_buffer (b0)->feature_arc_index;
  vnet_feature_config_main_t *cm = &fm->feature_config_mains[arc];

  return vnet_get_config_data (&cm->config_main,
			       &b0->current_config_index, next0,
			       n_data_bytes);
}

static_always_inline void
vnet_feature_next (u32 * next0, vlib_buffer_t * b0)
{
  vnet_feature_next_with_data (next0, b0, 0);
}

always_inline void *
vnet_get_config_data (vnet_config_main_t * cm,
		      u32 * config_index, u32 * next_index, u32 n_data_bytes)
{
  u32 i, n, *d;

  i = *config_index;

  d = heap_elt_at_index (cm->config_string_heap, i);

  n = round_pow2 (n_data_bytes, sizeof (d[0])) / sizeof (d[0]);

  /* Last 32 bits are next index. */
  *next_index = d[n];

  /* Advance config index to next config. */
  *config_index = (i + n + 1);

  /* Return config data to user for this feature. */
  return (void *) d;
}

feature實戰

后續補齊

參考文檔

https://blog.csdn.net/shaoyunzhe/article/details/53694092
https://wiki.fd.io/view/VPP/Feature_Arcs


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM