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 _pad[6],使其不再和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 _pad[6],使其不再和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.
相关推荐
峰顶听歌的鲸鱼2 小时前
32.Linux NFS 服务
linux·运维·服务器·笔记·学习方法
埃伊蟹黄面3 小时前
Linux基础开发工具 --- vim
linux·运维·服务器
zcz16071278213 小时前
自动化运维工具 Ansible 集中化管理服务器
linux·运维·服务器
東雪蓮☆3 小时前
Ansible Playbook 编写与模块详解
linux·运维·网络·ansible
iconball4 小时前
个人用云计算学习笔记 --17(DNS 服务器)
linux·运维·笔记·学习·云计算
iconball4 小时前
个人用云计算学习笔记 --16(DHCP 服务器)
linux·运维·笔记·学习·云计算
vortex54 小时前
在 Kali Linux 上配置 MySQL 服务器并实现 Windows 远程连接
linux·数据库·mysql
杨云龙UP5 小时前
CentOS 7上离线部署MySQL 8.0.X操作指南(二进制压缩包部署+独立目录部署,不在自动默认路径配置下安装)
linux·运维·服务器·mysql·centos
Ahu_iii5 小时前
【Linux】基础知识与操作汇总:一份给新手的 Linux 学习总结
linux·运维·学习