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

现象描述

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

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

Packet 503:50:43:447292: handoff_traceHANDED-OFF: from thread 2 trace index 5
03:50:43:447292: nat44-ed-out2inNAT44_OUT2IN_ED_FAST_PATH: sw_if_index 2, next index 12search key local 192.168.93.146:1 remote 192.168.1.123:1 proto ICMP fib 0 thre
ad-index 0 session-index 0no reason for slow path
03:50:43:447304: ip4-inaclINACL: sw_if_index 2, next_index 1, table_index -1, offset -1no table
03:50:43:447311: ip4-lookupfib 0 dpo-idx 15 flow hash: 0x00000000ICMP: 192.168.93.146 -> 192.168.1.123tos 0x00, ttl 64, length 1300, checksum 0x4f79 dscp CS0 ecn NON_ECNfragment id 0x2612, flags MORE_FRAGMENTSICMP echo_reply checksum 0x4ff2 id 1
03:50:43:447318: ip4-rewritetx_sw_if_index 1 dpo-idx 15 : ipv4 via 192.168.1.123 eth0: mtu:2026 next:8 fla
gs:[features ] 0019e0772dfb9409d30081280800 flow hash: 0x0000000000000000: 0019e0772dfb9409d3008128080045000514261220003f015079c0a85d92c0a800000020: 017b00004ff2000102cf6162636465666768696a6b6c6d6e6f707172
03:50:43:447325: nat44-in2out-output-worker-handoffNAT44_IN2OUT_WORKER_HANDOFF OUTPUT-FEATURE: next-worker 1 trace index 4
03:50:43:447335: nat44-ed-in2out-outputNAT44_IN2OUT_ED_FAST_PATH: sw_if_index 2, next index 5search key local 69.0.5.20:0 remote 38.18.32.0:0 proto IL fib 0 thread-index 0session-index 0
03:50:43:447341: nat44-ed-in2out-output-slowpathNAT44_IN2OUT_ED_SLOW_PATH: sw_if_index 2, next index 0
03:50:43:447355: error-droprx:eth1
03:50:43:447358: dropip4-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内存重叠。修改两者中的一个,另一个的值必然会被修改。

/* 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就会被修改。这就是为什么 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。       

/* *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.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/94962.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/94962.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入解析交换机端口安全:Sticky MAC的工作原理与应用实践

深入解析交换机端口安全:Sticky MAC的工作原理与应用实践在当今企业网络环境中,未授权设备接入是常见的安全威胁之一,而Sticky MAC技术正是解决这一问题的利器。在网络安全管理中,端口安全是保护网络基础设施的第一道防线。Sticky…

AI接管浏览器:Anthropic发布Claude for Chrome,是效率革命还是安全噩梦?

AI智能体(Agent)的竞赛,正在以超乎想象的速度进入白热化阶段。 就在上个月,OpenAI刚刚凭借ChatGPT Agent,向世界展示了AI在云端远程操作电脑、制作PPT的强大能力。而现在,它的老对手Anthropic,…

LFI-labs靶场通关教程

目录 CMD01-06 pass01 pass02 pass03 pass04 pass05 pass06 HDR-1 hdr-1 LFI-01-14 pass01 pass02 pass03 pass04 pass05 pass06 pass07 pass08 pass09 pass10 pass11 pass12 pass13 pass14 CMD01-06 pass01 看看源码, 这里显示的是一个get参数cmd,并…

随机森林的 “Bootstrap 采样” 与 “特征随机选择”:如何避免过拟合?(附分类 / 回归任务实战)

随机森林的 “Bootstrap 采样” 与 “特征随机选择”:如何避免过拟合?(附分类 / 回归任务实战) 第一部分:揭开随机森林的神秘面纱 1.1 告别“过拟合”,拥抱更强大的模型 在机器学习的旅程中,…

Java开发 - 缓存

一、RedisUtil封装package com.qj.redis.util;import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component;import javax.annotation.Resource; import java.util.Set; import java.util.…

光伏发多少电才够用?匹配家庭用电需求

在“双碳”目标推动下,新能源产业迎来爆发式增长,家庭屋顶光伏凭借清洁环保、能降低电费的优势,成为越来越多家庭的选择。但很多家庭在安装前都会陷入一个核心困惑:到底装多大容量的光伏系统,发多少电才能刚好满足自家…

如何管理跨境电商多语种素材?数字资产本地化指南

核心要点: 问题: 多语言内容管理真的那么难吗?多语种内容素材杂乱、反复翻译浪费预算、上线延迟影响市场窗口期,跨境电商如何高效管理全球素材? 答案: 借助 AI 驱动的数字资产管理系统,跨境品…

Git 8 ,git 分支开发( 切换分支开发,并设置远程仓库默认分支 )

目录 前言 一、📍环境背景 二、💻 完整流程 三、📝 顺序总览 四、🔹关系图例 五、❗暂存警告 六、☑️ 默认分支 七、🟣更多操作 前言 在团队开发或多人协作的项目中,Git 是最常用的版本管理工具。一个常见…

如何在mysql中执行创建数据库的脚本文件?

1、先准备好脚本文件,.sql扩展名的把脚本文件放在某个盘的根目录(也可以不是根目录,根目录的话路径会简单一些),这里我放在C盘的根目录下。脚本文件内容如下:/* SQLyog Community v13.1.1 (32 bit) MySQL - 5.7.26 : D…

《AI智脉速递》2025 年 8 月22 日 - 29 日

欧盟 AI 法案正式生效:禁止社会评分,规范生成式 AI 内容标注 8 月 21 日,欧盟《人工智能法案》全面实施,明确禁止社会评分、实时面部识别等高风险 AI 应用,要求生成式 AI 内容必须标注来源。该法案被视为全球最严格的 …

iOS 审核 4.3a【二进制加固】

我们应该知道,面对iOS 上架 遇到4.3a的问题或者制作马甲包.最基础的操作就是混淆代码尤其是我们专业做上架的,需要对各种语言的编译模式,产物,以及ipa构成都需要非常了解, 每种语言开发的App的编译产物不同,针对不同的编译产物做不同的处理方式有一些经验的开发者, 应该知道 目…

使用Python脚本执行Git命令

说明:本文介绍如何使用Python脚本在某个目录下执行Git命令 编码 直接上代码 import os import subprocessdef open_git_bash_and_run_command(folder_path, git_command):# 检查文件夹路径是否存在if not os.path.exists(folder_path):print(f"错误&#xff1a…

2025docker快速部署Nginx UI可视化管理平台

1、nginx-ui简介 Nginx UI 是一个开源项目,旨在为著名的 Web 服务器和反向代理软件 Nginx 提供一个基于网页的图形化用户界面(GUI)。它的核心目标是让 Nginx 的配置和管理变得可视化、简单化和自动化,从而降低其使用门槛&#xf…

数据防泄与最小可见:ABP 统一封装行级安全(RLS)+ 列级脱敏

数据防泄与最小可见:ABP 统一封装行级安全(RLS) 列级脱敏 TL;DR:把“谁能看到哪些行、字段可见到哪一位”下沉到数据库强制层(PostgreSQL:RLS 安全视图;SQL Server:RLS DDM&#x…

网络编程 04:TCP连接,客户端与服务器的区别,实现 TCP 聊天及文件上传,Tomcat 的简单使用

一、概述 记录时间 [2025-08-29] 前置文章: 网络编程 01:计算机网络概述,网络的作用,网络通信的要素,以及网络通信协议与分层模型 网络编程 02:IP 地址,IP 地址的作用、分类,通过 …

最小生成树——Kruskal

标题什么是生成树? 对于一张无向图,由nnn个顶点和n−1n-1n−1条边构成地联通子图,叫做这个无向图 生成树 最小生成树就是指边权之和最小的生成树 如何求最小生成树? Kruskal 介绍: 存图时只存每条边地起点、终点,…

ADFS 和 OAuth 的区别

ADFS 和 OAuth 的区别 ADFS(Active Directory Federation Services)和 OAuth 都是身份认证与授权领域的技术,但它们的设计目标、应用场景和实现方式有显著区别。以下从核心定义、技术特性、应用场景等方面详细对比: 核心定义与设计目标 技术 核心定义 设计目标 ADFS 微软…

神经网络参数量计算详解

1. 神经网络参数量计算基本原理 1.1 什么是神经网络参数 神经网络的参数主要包括: 权重(Weights):连接不同神经元之间的权重矩阵偏置(Bias):每个神经元的偏置项批归一化参数:BatchNo…

手写链路追踪

1. 什么是链路追踪 链路追踪是指在分布式系统中,将一次请求的处理过程进行记录并聚合展示的一种方法。目的是将一次分布式请求的调用情况集中在一处展示,如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等。这样就可以轻松了解…

从零开始的python学习——常量与变量

ʕ • ᴥ • ʔ づ♡ど 🎉 欢迎点赞支持🎉 个人主页:励志不掉头发的内向程序员; 专栏主页:python学习专栏; 文章目录 前言 一、常量和表达式 二、变量类型 2.1、什么是变量 2.2、变量语法 (1&a…