vpp开启nat,分片包丢包问题分析与解决

​

现象描述

两个网口都开启nat output-feature,路由模式进行大包转发,网络不同,小包转发没问题。

通过trace发现,在nat44-ed-in2out-output-slowpath节点丢包。

yaml 复制代码
Packet 5

03:50:43:447292: handoff_trace
  HANDED-OFF: from thread 2 trace index 5
03:50:43:447292: nat44-ed-out2in
  NAT44_OUT2IN_ED_FAST_PATH: sw_if_index 2, next index 12
  search key local 192.168.93.146:1 remote 192.168.1.123:1 proto ICMP fib 0 thre
ad-index 0 session-index 0
  no reason for slow path
03:50:43:447304: ip4-inacl
  INACL: sw_if_index 2, next_index 1, table_index -1, offset -1
      no table
03:50:43:447311: ip4-lookup
  fib 0 dpo-idx 15 flow hash: 0x00000000
  ICMP: 192.168.93.146 -> 192.168.1.123
    tos 0x00, ttl 64, length 1300, checksum 0x4f79 dscp CS0 ecn NON_ECN
    fragment id 0x2612, flags MORE_FRAGMENTS
  ICMP echo_reply checksum 0x4ff2 id 1
03:50:43:447318: ip4-rewrite
  tx_sw_if_index 1 dpo-idx 15 : ipv4 via 192.168.1.123 eth0: mtu:2026 next:8 fla
gs:[features ] 0019e0772dfb9409d30081280800 flow hash: 0x00000000
  00000000: 0019e0772dfb9409d3008128080045000514261220003f015079c0a85d92c0a8
  00000020: 017b00004ff2000102cf6162636465666768696a6b6c6d6e6f707172
03:50:43:447325: nat44-in2out-output-worker-handoff
  NAT44_IN2OUT_WORKER_HANDOFF OUTPUT-FEATURE: next-worker 1 trace index 4
03:50:43:447335: nat44-ed-in2out-output
  NAT44_IN2OUT_ED_FAST_PATH: sw_if_index 2, next index 5
  search key local 69.0.5.20:0 remote 38.18.32.0:0 proto IL fib 0 thread-index 0
 session-index 0
03:50:43:447341: nat44-ed-in2out-output-slowpath
  NAT44_IN2OUT_ED_SLOW_PATH: sw_if_index 2, next index 0
03:50:43:447355: error-drop
  rx:eth1
03:50:43:447358: drop
  ip4-input: input ACL session deny drops

在函数nat44_ed_in2out_slow_path_node_fn_inline中加调试信息发现vnet_buffer (b0)->ip.reass.save_rewrite_length值为2,正常应该是14。

在重组节点(ip4-sv-reassembly-feature),添加调试,发现下面的代码后ip.reass.save_rewrite_length会被修改成2。

分析了下vnet_buffer_opaque_t结构,终于明白了原因。

原因总结

vnet_buffer_opaque_t 结构中 ip.reass.owner_thread_index 和 ip.reass.save_rewrite_length内存重叠。修改两者中的一个,另一个的值必然会被修改。

c++ 复制代码
/* reassembly */
	union
	{
	  /* group input/output to simplify the code, this way
	   * we can handoff while keeping input variables intact */
	  struct
	  {
	    /* input variables */
	    struct
	    {
	      u32 next_index;	/* index of next node - used by custom apps */
	      u32 error_next_index;	/* index of next node if error - used by custom apps */
	    };
	    /* handoff variables */
	    struct
	    {
	      u16 owner_thread_index;
	    };
	  };
	  /* output variables */
	  struct
	  {
	    union
	    {
	      /* shallow virtual reassembly output variables */
	      struct
	      {
		u16 l4_src_port;	/* tcp/udp/icmp src port */
		u16 l4_dst_port;	/* tcp/udp/icmp dst port */
		u32 tcp_ack_number;
		u8 save_rewrite_length;
		u8 ip_proto;	/* protocol in ip header */
		u8 icmp_type_or_tcp_flags;
		u8 is_non_first_fragment : 1;
		u8 l4_layer_truncated : 7;
		u32 tcp_seq_number;
	      };
	      /* full reassembly output variables */
	      struct
	      {
		u16 estimated_mtu;	/* estimated MTU calculated during reassembly */
	      };
	    };
	  };
	  /* internal variables used during reassembly */
	  struct
	  {
	    u16 fragment_first;
	    u16 fragment_last;
	    u16 range_first;
	    u16 range_last;
	    u32 next_range_bi;
	    u16 ip6_frag_hdr_offset;
	  };
	} reass;

当同一组分片包被投放到不同线程时(nat handoff会将数据包投递到会话所在的线程),在重组节点(ip4-sv-reassembly-feature)会将线程ID记录到ip.reass.owner_thread_index中,如下图:

这时ip.reass.save_rewrite_length就会被修改。b035152127018&x-orig-expires=1759116719&x-orig-sign=htRfCGMSt%2FnHyM5LTFWpsdIXSyg%3D) 这就是为什么 ip.reass.save_rewrite_length 值为 2的原因,2是线程 id。

当数据包到达后续nat节点时,会通过ip.reass.save_rewrite_length获取IP头,这时获取的IP头时错误的,代码中无法识别3层协议而丢包。

尝试解决

修改 ip.reass.owner_thread_index 的位置,添加占位符 u8 _pad6,使其不再和ip.reass.save_rewrite_length内存重叠。如下:

​编辑

修改后还是会和tcp_seq_number有内存重叠,但TCP一般不会有分片包,问题不大。

经过上面的修改后,测试大包还是不通,又有了新的问题。O__O "...

新的问题

通过 trace 信息发现分片包(按照分片会话)没有按照预期被投递到别的线程(分片会话所在线程),并且进入ip4-sv-reassembly-feature(重组)节点,而是直接进入到后续的nat处理节点中,然后被丢包。丢包原因是ICMP type 无效。

分片包的ip.reass的icmp_type_or_tcp_flags等信息需要匹配到会话,然后被赋值(从分片会话中获取)。数据包没有再次进入p4-sv-reassembly-feature(重组)节点,就无法完成这一步。

当数据包到达后续nat节点时,因为icmp_type_or_tcp_flags无效被丢包。

继续找原因

通过 ip4_sv_reass_inline 函数代码发现,如果 error0 不为 IP4_ERROR_NONE 时会查feature来确定下一个节点,从而直接走后面的feature。

如上图中 next0 会被修改为nat44-out2in-worker-handoff节点,而分片包被投递到分片会话所在的线程,这一操作是在节点 ip4-sv-reass-feature-hoff 中完成的。next0 在 handoff 处会被指定为IP4_SV_REASSEMBLY_NEXT_HANDOFF,数据包就会走到ip4-sv-reass-feature-hoff节点。该节点中会按照 ip.reass.owner_thread_index 记录的值将数据包投递到相应的线程。

尝试修改2

经过上面的分析,解决方法就是在 handoff 处需要将error0赋值为非IP4_ERROR_NONE的值。如下:

经过上面的修改后,测试大包还是不通,又有了新的问题。O__O ".........

新的问题2

数据包成功被投递到分片会话所在的线程,但在数据包在重组节点,即ip4_sv_reass_inline函数中被丢包。丢包位置如下:

进一步加调试信息,发现IP头的获取是有误的,分析代码发现是下面的问题

当数据包第二次进入 ip4_sv_reass_inline 函数时 is_output_feature 是为 0 的。

经过分析 ip4-sv-reass-feature-hoff 节点代码,发现数据包的下一个节点会被固定指定为ip4-sv-reassembly(在函数ip4_sv_reass_init_function初始化指定),该节点在调用 ip4_sv_reass_inline 时会指定 is_output_feature 为 0。

这时的数据包处理是在路由后进行,正确的处理应该是在 ip4-sv-reass-feature-hoff 节点指定下一个节点为 ip4-sv-reassembly-output-feature。

尝试修改3

按照上面的分析进行如下修改:

  • 为函数ip4_sv_reass_handoff_node_inline添加参数 bool is_output_feature,然后新增节点 ip4-sv-reass-feature-hoff-output-feature,该节点在调用函数 ip4_sv_reass_handoff_node_inline 时指定参数 is_output_feature 为 1。
ini 复制代码
/* *INDENT-OFF* */
VLIB_NODE_FN (ip4_sv_reass_feature_handoff_node_output_feature) (vlib_main_t * vm,
						    vlib_node_runtime_t *
						    node,
						    vlib_frame_t * frame)
{
  return ip4_sv_reass_handoff_node_inline (
    vm, node, frame, true /* is_feature */, false /* is_custom_context */, true);
}
/* *INDENT-ON* */


/* *INDENT-OFF* */
VLIB_REGISTER_NODE (ip4_sv_reass_feature_handoff_node_output_feature) = {
  .name = "ip4-sv-reass-feature-hoff-output-feature",
  .vector_size = sizeof (u32),
  .n_errors = ARRAY_LEN(ip4_sv_reass_handoff_error_strings),
  .error_strings = ip4_sv_reass_handoff_error_strings,
  .format_trace = format_ip4_sv_reass_handoff_trace,

  .n_next_nodes = 1,

  .next_nodes = {
    [0] = "error-drop",
  },
};
/* *INDENT-ON* */
  • 修改ip4-sv-reassembly-output-feature节点的next_node,当 is_output_feature 为 1 时,让数据包走新增的节点 ip4-sv-reass-feature-hoff-output-feature。
  • 在 ip4_sv_reass_handoff_node_inline 函数中,如果 is_output_feature 为 1,则指定数据包的下一个节点为 ip4-sv-reassembly-output-feature。
  • 在函数 ip4_sv_reass_inline 中的 handoff 处,如果 is_output_feature 为 1,则指定数据包的下一个节点为 ip4-sv-reass-feature-hoff-output-feature。

rm->fq_index_output_feature 和 rm->fq_feature_index 在函数 ip4_sv_reass_init_function 中初始化,如下:

总结

vpp对于分片的处理支持的并不是很好,本次遇到问题主要是两方面的原因:

  1. vnet_buffer_opaque_t 联合体的设计,导致变量内存重,进而导致互相影响。
  2. nat handoff 选择线程与重组 handoff 选择线程的算法不同,导致数据包被多次从一个线程投递到其他线程。
  3. 而重组 handoff 对于数据包的处理存在一定的问题,即上面遇到的后两个问题。

所有的修改总结如下:

  • 修改结构体vnet_buffer_opaque_t 中 ip.reass.owner_thread_index 的位置,添加占位符 u8 _pad6,使其不再和ip.reass.save_rewrite_length内存重叠。
  • ip4_sv_reass_inline 中的 handoff 处,进行两处修改。如果 is_output_feature 为 1,则指定数据包的下一个节点为 ip4-sv-reass-feature-hoff-output-feature;指定error0 = IP4_ERROR_REASS_INTERNAL_ERROR;
  • 新增 ip4-sv-reass-feature-hoff-output-feature 节点,让数据包在路由后正确被处理。详细见上面的修改3.
相关推荐
雯宝1 小时前
mnt_init 函数
linux
律宏阔7 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔7 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴7 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千7 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei377 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei377 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei377 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
GeW7 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux
GeW7 小时前
工业场景中的Linux与数据库选型:高端化转型的技术底座
linux