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