vpp nat44 handoff数据包

本文深入解析VPP中的线程间数据包调度(handoff)机制,特别是nat44场景下如何通过hash算法确保同一用户数据包始终落在同一工作线程,以及数据包如何从发送线程高效传递至接收线程的具体流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

vpp本身支持线程之间的数据包调度——handoff。可以用这种特性来分发数据包至指定线程。

这里就从nat44的handoff开始来分析一下。简化掉拥塞,trace等处理,只看核心逻辑。

参考vpp代码版本号:2005

[ckun@localhost vpp]$ git status
# On branch 2005

初始化

设定nat44接口

snat_feature_command_fn函数

1. enable nat44-in2out-worker-handoff/nat44-out2in-worker-handoff  node

2. 初始化分发队列 vlib_frame_queue_main_init

 

vlib_frame_queue_main_init函数

1. new 一个vlib_frame_queue_main_t 结构, 追加到全局变量 vlib_thread_main的frame_queue_mains数组中。

    这个 vlib_thread_main.frame_queue_mains在线程轮询时会去check,并get其中的数据包。

2. vlib_frame_queue_main_t结构内的初始化

typedef struct
{
  u32 node_index;         // 分发后线程转到对应node处理包
  u32 frame_queue_nelts;  // 队列中元素个数,默认64
  u32 queue_hi_thresh;    // 门限 64 - 线程数

  vlib_frame_queue_t **vlib_frame_queues;              // 线程数个frame_queues
  vlib_frame_queue_per_thread_data_t *per_thread_data; //线程数个thread data

  /* for frame queue tracing */
  frame_queue_trace_t *frame_queue_traces;
  frame_queue_nelt_counter_t *frame_queue_histogram;
} vlib_frame_queue_main_t;

vlib_frame_queues

申请线程数个队列,每个队列中申请64个元素

typedef struct
{
  /* enqueue side */
  CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
  volatile u64 tail;
  u64 enqueues;
  u64 enqueue_ticks;
  u64 enqueue_vectors;
  u32 enqueue_full_events;

  /* dequeue side */
    CLIB_CACHE_LINE_ALIGN_MARK (cacheline1);
  volatile u64 head;
  u64 dequeues;
  u64 dequeue_ticks;
  u64 dequeue_vectors;
  u64 trace;
  u64 vector_threshold;

  /* dequeue hint to enqueue side */
    CLIB_CACHE_LINE_ALIGN_MARK (cacheline2);
  volatile u64 head_hint;

  /* read-only, constant, shared */
    CLIB_CACHE_LINE_ALIGN_MARK (cacheline3);
  vlib_frame_queue_elt_t *elts;    // 64个元素
  u32 nelts;                       // 64个
}
vlib_frame_queue_t;

per_thread_data

申请线程数个thread_data。

每个thread_data中,再申请线程数个handoff_queue_elt_by_thread_index

typedef struct
{
  vlib_frame_queue_elt_t **handoff_queue_elt_by_thread_index;  // 记录本线程转发至目的线程的队列
  vlib_frame_queue_t **congested_handoff_queue_by_thread_index; //判断阻塞用的
} vlib_frame_queue_per_thread_data_t;

数据包分发至队列

1. 为数据包选择目的线程

这里按照数据包源地址哈希确定目的线程,确保某一用户的数据包一定落在同一个线程。

这里是in2out的例子,out2in根据端口决定线程,也能确保同一个用户一个线程。

static u32
snat_get_worker_in2out_cb (ip4_header_t * ip0, u32 rx_fib_index0,
			   u8 is_output)
{
  snat_main_t *sm = &snat_main;
  u32 next_worker_index = 0;
  u32 hash;

  next_worker_index = sm->first_worker_index;
  hash = ip0->src_address.as_u32 + (ip0->src_address.as_u32 >> 8) +
    (ip0->src_address.as_u32 >> 16) + (ip0->src_address.as_u32 >> 24);

  if (PREDICT_TRUE (is_pow2 (_vec_len (sm->workers))))
    next_worker_index += sm->workers[hash & (_vec_len (sm->workers) - 1)];
  else
    next_worker_index += sm->workers[hash % _vec_len (sm->workers)];

  return next_worker_index;
}

2. 将数据包塞入分发队列 vlib_buffer_enqueue_to_thread

vlib_buffer_enqueue_to_thread (vlib_main_t * vm, 
                                u32 frame_queue_index, // fqm的index,在vlib_thead_main.frame_queue_mains中
                                u32 * buffer_indices,  // 数据包的index们
                                u16 * thread_indices,  // 目的线程标识,一个数据包对应一个线程id
                                u32 n_packets,         // 数据包数
                                int drop_on_congestion)

1. 获取队列元素

  (每一个包处理)

  vlib_frame_queue_elt_t*  hf  =   fqm->vlib_frame_queues[worker_thread_index]->elts[x] 

  从对应线程队列取一个可用的元素(闲置slot)

2. 将数据包index放入 hf->buffer_index数组中。

N个包处理完,某个线程的hf->buffer_index中会有多个数据包索引。

同时

fqm->per_thread_data[current_thread_index]->handoff_queue_elt_by_thread_index[worker_thread_index] = hf;

3. 遍历handoff_queue_elt_by_thread_index,获得每个操作过的hf

hf置为可用,工作线程置为需检查分发队列

	      vlib_put_frame_queue_elt (hf); // hf->valid = 1
	      vlib_mains[i]->check_frame_queues = 1;
	      ptd->handoff_queue_elt_by_thread_index[i] = 0;

从队列取数据包

vlib_main_or_worker_loop 函数

函数循环体中,遍历所有fqm,尝试从fqm中取包   

      if (!is_main)
	{
	  vlib_worker_thread_barrier_check ();
	  if (PREDICT_FALSE (vm->check_frame_queues +
			     frame_queue_check_counter))
	    {
	      u32 processed = 0;

	      if (vm->check_frame_queues)
		{
		  frame_queue_check_counter = 100;
		  vm->check_frame_queues = 0;
		}

	      vec_foreach (fqm, tm->frame_queue_mains)        //遍历所有fqm
		processed += vlib_frame_queue_dequeue (vm, fqm);

	      /* No handoff queue work found? */
	      if (processed)
		frame_queue_check_counter = 100;
	      else
		frame_queue_check_counter--;
	    }
	  if (PREDICT_FALSE (vec_len (vm->worker_thread_main_loop_callbacks)))
	    clib_call_callbacks (vm->worker_thread_main_loop_callbacks, vm);
	}

vlib_frame_queue_dequeue

1. 获取当前线程的队列 fq = fqm->vlib_frame_queues[worker_thread_index]

2.  fp->elts获取每一个有效的elt(etl->valid ==1) 

3. 新建一个frame数据帧,隶属于fqm->node_index

4. 将elt元素中存有多个buffer_index, 传入到新建的frame数据帧里。

 

从而数据包顺利转入至目标线程和目标node。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值