Linux nf_conntrack调优:容量、buckets与超时排查
发布时间:2026/10/23 3:55:22 锦皓数字建站

凌晨两点被电话叫起来的那种事做过几年运维的人大概都碰过。监控页面上业务端口还活着但新连接的成功率掉到七成内核日志里一行行刷着nf_conntrack: table full, dropping packet。把业务进程重启一下能好十分钟然后继续掉。真正的原因跟业务代码一点关系都没有——Netfilter 的连接跟踪表被打满了。而围绕这张表的所有参数nf_conntrack_max、nf_conntrack_buckets、各种 timeout恰恰是 Linux Netfilter 内核参数调优里最容易被抄一份网上模板糊弄过去的一块。这篇东西想聊的就是这块。我不打算给你一份可以直接粘贴的sysctl.conf因为那种模板九成的内容对你的业务没有意义剩下那一成还可能因为场景不匹配把问题搞得更糟。我想讲清楚的是这些数字背后的账是怎么算的什么情况下该动哪个、动了之后会付出什么代价、出了问题怎么顺着链路查下去。读者画像大概是这样手上有几台跑 NAT、网关、反向代理或者高并发短连接的机器被连接数、丢包、CPU 软中断中的某一项折磨过想在调参这件事上从抄变成算。如果你只是想找几条命令这篇文章可能会让你觉得啰嗦如果你想以后不再被同一类问题反复咬那接着往下看。1. 调参之前先看清Netfilter 在数据包路径上到底插了几道手很多人对 Netfilter 的印象停留在iptables 就是防火墙于是调参时脑子里只有规则条数、表链顺序这些概念。但真正吃掉你内存和 CPU 的往往不是规则匹配而是连接跟踪。这两件事在数据包路径上是先后关系而且前者的开销远大于后者——不清这一点后面所有的参数都会调错方向。1.1 五个 hook 点对应的是五类完全不同的开销Netfilter 在协议栈里埋了五个挂载点PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。这四个表 filter、nat、mangle、raw 分布在这些点上但开销性质完全不同。raw表在最前面作用是跳过连接跟踪一旦命中NOTRACK后面的 conntrack 就不干活了mangle改包头的字段涉及校验和重算nat只在连接的第一个包上做决策后续包靠 conntrack 里存好的映射直接转换filter是纯匹配规则多的时候走的是逐条比对但现代内核有哈希化的匹配优化规则上千条也未必是瓶颈。真正的分水岭在于只要数据包没有被raw表放行跳过它就必须先进 conntrack 建一条记录。这意味着哪怕你一条 filter 规则都没写一张空表只要机器在转发流量conntrack 表就在长。很多人第一次遇到table full的时候一脸茫然因为他压根没配过任何防火墙规则——就是这个原因。1.2 conntrack 是先记账后过滤账单在 filter 之前就产生了用生活化的说法conntrack 像小区门口的访客登记本filter 像保安的判断。访客要先登记保安才决定放不放行。登记本写满了后面的人连被判断的机会都没有直接在门口被拦下——这就是table full, dropping packet的本质包在 conntrack 阶段就被丢了你的 filter 规则根本没被执行。所以从调优角度你面对的是两层资源记录条目的数量内存和查表的效率CPU。前者由nf_conntrack_max控制后者由哈希桶的数量nf_conntrack_buckets控制两者之间还有一个隐含的耦合关系后面会细讲。而超时参数决定了每条记录在表里躺多久等于间接控制了表的周转率。这三个维度构成一个三角只动一角另外两角就会反过来咬你。1.3 我习惯先画一张流量图再改任何数字在真的动手改/proc/sys/net/netfilter/下的东西之前我会先在纸上写清楚四件事这台机器的角色是什么转发网关、NAT 出口、反向代理、还是纯终端服务器流量的连接形态是什么长连接还是短连接新建速率大概多少平均持续多久有没有经过二次 NAT 或者容器网络内存总共有多少、可以给内核网络栈分配多少。这四件事决定了后面每一个参数的取值逻辑。举个对比例子一台做七层反向代理的机器并发连接几十万但新建速率不高nf_conntrack_max要按并发数给足超时可以放宽一台做 NAT 出口的机器新建连接每秒上万但单条连接活得短这时候max反而不是重点buckets和time_wait超时才是关键。同一个nf_conntrack_max 1048576放在这两台机器上一台绰绰有余一台可能还是会打满。参数没有绝对的好坏只有跟流量形态配不配。2. nf_conntrack_max 不该拍脑袋把同时在线连接数算成内存账nf_conntrack_max是所有相关文章里出现频率最高的一个数也是最容易被随手写成1048576或者655360的一个数。我见过有人把它设成 4194304机器内存 8G然后抱怨内核动不动 OOM。这个参数的本质是允许同时存在的记录条数上限它的约束条件只有一个——内存。2.1 一条 conntrack 记录的真实体积怎么量不同内核版本、不同编译选项、是否开启扩展如记账、时间戳、标签都会影响单条记录的大小网上流传的约 300 字节只是一个粗略参考。想拿到自己机器上的真实值最直接的办法是查 slabgrep -E ^nf_conntrack\s /proc/slabinfo输出里的第五列是objsize即单个对象占用的字节数。我在几台不同发行版上看到过的值分布在 280 到 400 之间开了nf_conntrack_acct之后会明显变大。拿到这个值估算公式就很朴素了内存上限 ≈ nf_conntrack_max × objsize比如objsize是 320 字节max设成 1048576理论上限就是约 320MB。注意这里是上限不是常驻占用——内核是按需分配记录的表里没有那么多条目就不会占那么多内存。但你必须按上限来规划因为流量洪峰来的时候它会真的长到那么大。2.2 用并发峰值而不是 QPS 推 max一个常见的错误算法是我的 QPS 是 5 万所以 max 设 5 万。这是把速率和存量搞混了。conntrack 表里躺的是当前存活的连接不是每秒新建的连接。正确做法是找出业务的历史并发峰值再留出余量目标 max 并发连接峰值 × 1.5 ~ 2余量为什么给这么多因为超时时间的存在意味着连接关闭后记录还会残留一段时间time_wait、fin_wait、close各有各的残留时长实际表里的条目数总是大于业务视角看到的活跃连接数。另外突发流量、扫描流量、健康检查流量都会额外占用条目这些在你的业务监控里通常是看不见的。如果拿不到准确的并发数据退一步的办法是观察/proc/sys/net/netfilter/nf_conntrack_count的历史曲线。这个文件是只读的反映当前条目数。采集它一段时间取 P99 或者最大值再乘 1.5比拍脑袋靠谱得多。2.3 buckets 与 max 的比例以及哈希冲突的代价这是被忽略得最厉害的一环。conntrack 内部是一张哈希表buckets就是桶的数量。每次查表、插入都要先算哈希落到某个桶再在桶内的链表里线性比对。桶太少会发生什么大量记录挤在少数几个桶里链表变长查找从 O(1) 退化成近似 O(n)表现出来就是ksoftirqdCPU 飙高、conntrack -S里search_restart计数上涨、网络延迟抖动。内核的默认策略在不同版本里不太一样早期是按max/4推算后来有版本改成max/2甚至更大。但默认值在max被手工调大之后就不会自动跟着变了——这是关键的坑你把max从 65536 调到 1048576buckets可能还停在 16384平均每条链路上挂着 64 条记录。我的经验比例是buckets ≈ max / 4在短连接高新建场景下会调到max / 2。桶本身是哈希表头数组每个桶占一个指针8 字节buckets 262144时表头内存才 2MB可以放心加。真正限制你的是缓存局部性——桶太多会让哈希表本身撑出 CPU 缓存所以也不是越大越好max/2到max/4这个区间基本够用。2.4 一份可以直接抄的容量配置假设一台 32G 内存的 NAT 出口机objsize实测 320业务并发峰值 30 万新建速率较高参数取值依据nf_conntrack_max78643230 万 × 2 余量内存上限约 240MBnf_conntrack_buckets196608max/4表头约 1.5MBnf_conntrack_tcp_timeout_established1200出口 NAT 场景配合下游保活nf_conntrack_tcp_timeout_time_wait30短连接多压缩残留nf_conntrack_udp_timeout30保持默认即可这张表里的每一个数字都有推导过程换一台机器就要重算不能照抄。尤其是established那一项下面单独说。3. 超时时间是藏在幕后的第二个容量开关把max调大是最容易想到的办法但它有一个前提你得有足够的内存。在内存吃紧或者业务不允许扩内存的场景下动超时参数往往性价比更高。原因很简单——表里的条目数约等于新建速率 × 平均存活时长。速率不变的前提下把存活时长砍一半表就小一半。3.1 五天的 established 默认值谁在用谁吃亏nf_conntrack_tcp_timeout_established的默认值是 432000 秒也就是 5 天。这个数字的历史原因很朴素TCP 连接理论上可以无限期存活干脆给一个足够大的值保证不误杀。但放到今天的业务场景里它带来的后果是——一条早就断掉的连接可能还会在 conntrack 表里占着位置好几天。为什么断掉的连接还在因为 TCP 是双向的如果一端异常断电、网络闪断、或者中间设备静默丢包另一端可能收不到 FIN 或 RST连接就处于一个双方都不动的状态。业务层面有超时机制会自己清理但 conntrack 不知道它只会在超时到期时回收。5 天的默认值意味着这类僵尸条目会持续堆积。我的取值习惯是短连接为主的场景HTTP API、数据库连接池短连设 600 到 1800 秒长连接场景WebSocket、消息推送、数据库长连接设 3600 秒左右同时确保应用层有心跳保活。关键不是这个数字多大而是它必须大于应用层心跳间隔的若干倍。如果应用每 300 秒发一次心跳conntrack 超时设成 200 秒那每条连接都会被误判回收后续包会被标记为INVALID直接丢掉症状是连接看起来没断但一发包就报错。3.2 time_wait、udp、generic 该分别怎么砍不同协议状态的超时应该分开看待一刀切会出问题nf_conntrack_tcp_timeout_time_wait默认 120 秒高并发短连接场景下这是最值得砍的一项。TIME_WAIT 状态的记录已经没有数据要传了纯粹在等待端口复用窗口压到 30 秒甚至更短一般不会有副作用只要你的ip_local_port_range足够宽。nf_conntrack_udp_timeout默认 30 秒UDP 无连接靠这个超时判断流结束。DNS、监控上报这类场景 30 秒合理如果是 QUIC 或者游戏类长 UDP 流要注意nf_conntrack_udp_timeout_stream它管的是看到双向包之后的超时默认在 120 到 180 秒之间不同内核版本有差异。nf_conntrack_generic_timeout默认 600 秒兜底值管 ICMP 之外的杂项协议。除非你跑 SCTP 之类的一般不用管。nf_conntrack_icmp_timeout默认 30 秒ping 洪水场景下会被大量占用正常业务保持默认即可。砍超时之前务必确认一件事这些连接在超时被回收之后如果还有包过来会怎样。答会被判定为新连接重新走一遍建链流程。对于 TCP 来说如果对方还在旧连接上发包这个包不满足新连接的握手条件会被标记INVALID。如果你的防火墙规则里有DROP INVALID通常都有包就被丢了。这就是下面要说的反例。3.3 砍超时会踩到的两个正确性反例第一个反例是跨设备超时不一致。内部 conntrack 设了 3600 秒但上游有一个运营商的 NAT 设备只保留 300 秒。当上游静默丢弃了映射关系内部还认为连接有效于是继续往上发——包出去了回不来。表现出来就是连接不断开也不通客户端一直卡着等超时。这类问题的定位非常费劲因为你在本机抓包看到的都是我发了没回。解决思路是让 conntrack 超时小于或等于链路上最短的那个设备超时靠应用层保活来维持连接。第二个反例是负载均衡器后面的长连接。后端服务的 conntrack 超时被压到 600 秒而前端 LB 到后端用的是长连接LB 侧的空闲超时是 900 秒。结果就是后端先回收LB 再次复用这条连接发包时被丢用户看到偶发的 502。这类问题概率低、复现难但一旦发生就是持续性故障。调超时之前一定要把整条链路上所有设备的空闲超时列出来取最小值作为上限。4. 表满之后一条从告警到根因的完整排查链路参数调完了不等于问题解决了。真出故障的时候光知道该改哪个参数没用得能快速判断这次是容量不够还是条目泄漏还是别的什么在伪装成表满。这一节把我自己走过很多次的排查路径完整写一遍。4.1 先分清是容量不够还是条目泄漏第一步永远是看当前值和上限cat /proc/sys/net/netfilter/nf_conntrack_count cat /proc/sys/net/netfilter/nf_conntrack_max如果count贴着max上下浮动说明容量确实不够。但这里有个细节内核在count达到上限时不会立刻丢弃它会先尝试从当前 CPU 的哈希桶里挑一个没有确认的、最早建立的条目踢掉这就是early_drop。所以真实的表满可能表现为count略低于max但early_drop和drop计数在涨。第二步看增长的形态。如果count是随业务流量同步起伏的那是容量问题加max或者砍超时。如果count是台阶式单调上涨、业务闲时也不回落那大概率是条目没有被正确回收——查的方向就变成哪类连接在堆积。用下面的命令按协议和状态分组看conntrack -L -o extended 2/dev/null | awk {print $1, $4} | sort | uniq -c | sort -rn | head -20输出里如果某一类比如某个特定端口的TIME_WAIT占了绝大多数问题就定位到具体业务了。4.2 conntrack -S 的四个计数器各自在说什么conntrack -S输出的是 per-CPU 的统计几个关键字段的含义值得记牢字段含义涨了说明什么insert成功插入的记录数正常增长反映新建速率insert_failed插入失败数与drop基本同步表满的直接证据early_drop提前回收的条目数表接近上限开始踢老记录drop最终丢弃的包数已经影响到业务search_restart查找冲突重启次数偏大说明哈希冲突严重桶太少invalid被判无效的包数超时设置与业务不匹配或状态跟踪异常search_restart这个字段容易被忽略但它是buckets配置是否合理的直接指标。如果它随着流量增长线性上升而count并不高那就是桶太少导致的链表过长这时候调max完全没用得调buckets。4.3 三种常见的假象别被它们带偏假象一容器里看到的 count 很小宿主机却满了。较新的内核支持 conntrack 按网络命名空间隔离容器里读到的/proc/sys/net/netfilter/nf_conntrack_count是容器自己的表宿主机完全是另一套。排查容器网络问题时必须在宿主机命名空间里看或者用nsenter进入正确的 netns。这一条坑过我不止一次。假象二max调大之后问题依旧。常见原因是max只改在了运行时没写进持久化配置机器一重启就回到默认值或者改的时候写错了层级比如写到/etc/sysctl.conf但系统实际读的是/etc/sysctl.d/目录且顺序被覆盖。还有一种情况是max确实改了但buckets没跟上于是包不再被丢但 CPU 软中断先爆了。假象三业务侧显示连接数很低误判为没有并发。业务侧统计的通常是活跃请求数或者已完成握手的连接数而 conntrack 统计的是所有被跟踪过的四元组包含大量半开连接、健康检查、扫描流量。两者数量级可能差好几倍不要拿业务指标直接推 conntrack 容量。4.4 处置顺序先止血再改配置真在线上遇到表满我的顺序是这样的止血临时把nf_conntrack_max顶上去内存允许的前提下先让业务恢复。命令是sysctl -w net.netfilter.nf_conntrack_maxxxx立即生效。观察确认drop停止增长、count稳定在新的水位。这时候不要急着继续调先看十分钟。定位按 4.1 的方法查是哪类连接在堆积。如果是泄漏光调大max只是把故障时间往后推。修正根据定位结果调整超时或者业务侧连接管理。固化把变更写进持久化配置并补上监控项——nf_conntrack_count / nf_conntrack_max的比值超过 0.7 就该告警不要等它满了才发现。5. 和 conntrack 绑在一起的几个参数抄错了比不调更糟除了容量和超时这两大类还有几个参数经常出现在各种内核优化模板里但它们的行为并不像名字看起来那么无害。用错了会把一类原本能正常工作的流量直接打挂。5.1 tcp_loose 与 be_liberal放宽判定换来的代价nf_conntrack_tcp_loose默认是 1含义是如果收到的包不满足 TCP 状态机的合法转移也允许为它建立跟踪记录。这个默认值对非对称路由、抓包工具、以及那些连接中途才经过本机的场景很友好。设成 0 之后只有看到完整的 SYN 握手才会建记录能省一点资源但代价是所有半路经过的连接都会被判无效。nf_conntrack_tcp_be_liberal默认是 0设成 1 之后会放宽窗口检查——窗口外的包不再被标记INVALID。这个参数在遇到老式设备乱发窗口外报文时能救急但它同时会削弱 conntrack 对异常包的识别能力等于你把一道安全检查关掉了。我的建议是除非有明确的兼容性需求否则保持默认。还有一个nf_conntrack_tcp_max_retrans默认 3控制一条连接最多容忍多少次重传超过就认为失效并提前回收。在高丢包链路上这个值设太小会导致连接被误杀设太大又会让僵死连接占着位置。3 是一个经验平衡点一般不用动。5.2 helper 自动加载与 events 的隐性开销nf_conntrack_helper从较新内核开始默认关闭这是个好变化。早期默认开启时任何一个连到 21 端口的 FTP 流量都会触发内核自动加载 FTP helper而这个 helper 会解析应用层内容、动态开洞。这不仅带来开销还是一个不小的安全隐患——攻击者可以构造畸形报文触发解析漏洞。如果你的机器上还有老系统确认一下这个参数sysctl net.netfilter.nf_conntrack_helper值为 1 的话考虑关掉改用 iptables 的CT --helper显式指定只对必要的流量开启。nf_conntrack_events默认是 1作用是向用户态发送连接事件供 conntrackd 之类的工具做状态同步。如果你没有用状态同步工具这个参数等于白白产生 netlink 消息开销。关掉它能省一点但在高并发场景下省下的量其实有限属于优化边际收益较小的那一类。真正收益大的是nf_conntrack_acct和nf_conntrack_timestamp——这两个默认都是 0如果被人打开过有些监控方案会要求打开每条记录都会额外带计数器或时间戳objsize会明显变大。调max之前先确认这两个是关的否则你的内存估算会偏。5.3 端口范围、backlog 与 conntrack 的耦合这几个参数严格说不属于 netfilter但和连接跟踪的表现强相关经常被一起调net.ipv4.ip_local_port_range默认是32768 60999约 2.8 万个可用端口。做 NAT 出口的机器每个目标地址最多只能有这么多并发连接。如果你的nf_conntrack_max设到了上百万但端口范围没扩那瓶颈根本不在 conntrack而在端口耗尽——症状是cannot assign requested address。扩到10240 65000能到 5.4 万个。net.core.somaxconn和net.ipv4.tcp_max_syn_backlog管的是 accept 队列和半连接队列和 conntrack 是两套东西。但要注意半连接队列里的 SYN_RECV 连接也会在 conntrack 里占记录。所以 SYN 洪水攻击时你会同时看到半连接队列满和 conntrack 表满两个现象。这时候单纯调max是治标真正的解法是在上游或者本机做 SYN 限速。net.ipv4.tcp_tw_reuse值得单独提一句。它允许把 TIME_WAIT 状态的端口复用于新的出站连接能缓解端口耗尽。但复用的前提是时间戳满足条件在 NAT 场景下如果两端时间戳行为不一致可能引发连接异常。我的做法是稳态下不开只在高新建速率且端口确实不够时才开并且开之前先确认tcp_timestamps是启用的。6. 参数怎么落地模块参数、sysctl 与不停机变更前面讲的全是该设成多少这一节讲怎么设才不会在重启后失效。这是我见过的最多的低级失误来源——改了、生效了、跑了三个月一次重启回到解放前。6.1 hashsize 为什么不能只写在 sysctl.conf 里nf_conntrack_max和各类 timeout 都是 sysctl 接口写进/etc/sysctl.d/下的配置文件即可持久化。但buckets也就是 hashsize在不同内核版本里的暴露方式不一样老一些的版本只能通过模块参数设置路径是/sys/module/nf_conntrack/parameters/hashsize运行时可以写但重启后需要重新写较新的内核提供了net.netfilter.nf_conntrack_buckets这个 sysctl 键就可以和其他参数一起管理了。先确认你的内核支持哪种sysctl net.netfilter.nf_conntrack_buckets如果返回cannot stat那就走模块参数路线写一个/etc/modprobe.d/nf-conntrack.confoptions nf_conntrack hashsize196608这里有个前提nf_conntrack必须是模块而不是编译进内核的。检查方法是lsmod | grep nf_conntrack如果没有输出但功能正常工作那多半是内建的模块参数就不生效了只能在引导参数里加nf_conntrack.hashsize196608。6.2 一套带灰度与回滚的变更流程内核网络参数的变更有个特点绝大部分可以热改、立即生效、不需要重启。这既是好事也是坏事——好处是快坏处是出错也快而且影响面是全机器的。我的流程固定为四步。第一步先在一台非核心机器上验证改完观察至少一个业务高峰。第二步确认持久化配置文件写好并且放在正确的位置——注意/etc/sysctl.d/下的文件按文件名字典序加载后面的覆盖前面的所以自定义文件建议用99-前缀避免被发行版自带的配置覆盖。第三步用sysctl --system验证一遍加载过程没有报错特别留意 No such file or directory 这类提示它通常意味着nf_conntrack模块在 sysctl 执行时还没加载。第四步准备回滚脚本把改之前的原始值记录下来。关于模块加载顺序这个坑再强调一次。很多发行版上nf_conntrack模块是在网络服务启动时才被拉起的而systemd-sysctl服务执行得更早于是所有net.netfilter.*的配置在这一刻都会写入失败日志里一行警告很容易被忽略。解决办法有两个一是在/etc/modules-load.d/里显式声明nf_conntrack保证它在 sysctl 之前加载二是在 sysctl 配置里用-前缀忽略错误不推荐会把真正的问题也吃掉。我更倾向第一种因为它是从根上解决顺序问题。改完之后怎么验证真的生效了不要只看sysctl -a的输出那个值可能来自运行时而不是配置文件。正确的验证方式是重启一次或者在测试机上重启然后再读一遍同时检查dmesg里有没有 netfilter 相关的报错。生产环境不方便重启的至少要把配置文件内容和sysctl --system的输出对一遍。7. 这几年反复踩到的坑参数调优这件事讲原理是一回事真正让人长记性的是被坑。下面几条都是我自己或者身边同行真实遇到过的写出来省得你重走一遍。7.1 只调 max 不调 bucketsCPU 先扛不住这是最典型的一类。表不再满了包不再丢了但机器的ksoftirqd占用从 5% 涨到 40%网络延迟 P99 翻倍。原因就是前面说的哈希冲突——记录数翻了 16 倍桶还是原来那些每条链路上挂着一长串每次查表都要走一遍链表还要频繁触发search_restart。调max的时候必须同步检查buckets这两个是一对拆开就没有意义。判断方法很简单看conntrack -S里的search_restart是不是随流量明显增长。7.2 变更后指标变好其实是假象有一次把tcp_timeout_established从 432000 砍到 600nf_conntrack_count立刻从 40 万掉到 8 万所有人都觉得优化成功。结果两天后业务报障一批长连接业务在空闲十几分钟后首次请求必然超时一次。原因就是 600 秒的超时把那些应用层认为还活着但链路上没有包的连接回收了下一次发包被判INVALID。指标变好不代表业务变好调超时一定要配合应用层的连接存活时长来看光看 count 曲线会得出完全错误的结论。7.3 容器环境里两套视图排查方向完全跑偏在容器化环境里排查连接问题最容易犯的错是在容器内看指标。Conntrack 在新内核里是按 netns 隔离的容器里那张表通常很小怎么调都不会满而真正承载流量的是宿主机的那张表。正确姿势是进宿主机的 netns 看或者用nsenter -t pid -n切进去。另外容器编排平台自己也会管一套 conntrack 相关的配置改动之前最好确认一下它有没有在启动时覆盖你的设置。7.4 内存估算漏掉了扩展字段前面提过nf_conntrack_acct和nf_conntrack_timestamp打开之后单条记录会变大。我遇到过一台机器max设 200 万按 320 字节算应该是 640MB实际峰值吃掉了 1.2G。查下来是某个监控代理把acct打开了objsize涨到了 600 多。教训是估算内存不要用网上的通用值一定要在本机slabinfo里读实际值而且要在所有功能都开启的状态下读。最后分享一个小技巧。如果你不想每次都手动算可以在机器上放一个小小的巡检脚本定期把count、max、buckets、objsize、drop、search_restart这几个值打印出来顺便算一下比值。跑上几周你就能摸清这台机器的正常水位长什么样下次出问题的时候一眼就能看出哪个数字不对劲——这种直觉比任何调优模板都值钱。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。