Linux3.5内核以后的路由下一跳缓存

注意:免费节点订阅链接已更新至 2025-11-13点击查看详情

在Linux3.5版本(包括)之前,存在一个路由cache,这个路由cache的初衷是美好的,但是现实往往是令人遗憾的。

以下是陈列出的两个问题:

1.面临针对hash算法的ddos问题(描述该问题的文章已经汗牛充栋,不再赘述);

2.缓存出口设备是p2p设备的路由项会降低性能。

这 些问题本质上是由于路由cache的查找方式和路由表的查找方式互不相容引起的。路由cache必须是精确的元组匹配,因此它必须设计成一维的hash 表,而路由表查找算法是最前前缀匹配,因此它可以是多维的。路由查找最终会找到路由项,在不考虑策略路由的前提下,我们来看一下把出口设备为p2p设备的 路由箱塞进路由cache是多么的没有意义。

p2p设备的邻居集合里只有一个下一跳,那就是它的对端,因此对于p2p设备,甚至都不需要进行邻居绑定的过程!然而如果将这类路由塞进路由cache的 话,将会占据巨量的内存,试想如果有10w个IP地址需要通信,源IP集合中同样有10w个IP地址,将有可能会建立100w条路由cache项,极端一 点,如果此时系统中只有不多的几条路由表项的话,查找路由表的开销可能会反而低于查找路由cache的开销,特别低,如果路由结果是p2p设备,事实上只 要想办法cache这唯一的一个条目即可。这就是一和多的区别,这次,我们发现不光零到一有意义,一到多也同样不可小觑。

如果系统中有一块以太网卡eth0,由于同一网段会有多个邻居,不同的目标IP地址,其下一跳可能会有所不同,我们不得不cache每一个与eth0相关 的路由项,然后针对每一个数据包进行精确匹配,然而如果系统中有一块p2p网卡,它的邻居只有一个,对于点对点设备而言,其对端逻辑上只有一个设备,它是 唯一的且确定的,它是该点对点设备的邻居集合中的唯一一个邻居,因此事实上无需进行邻居绑定过程,只要从点对点设备将数据包发出,该数据包就一定会到达唯 一的对端,在这种情况下,如果我们还cache每一个与该p2p网卡相关的路由项,意义就不大了,然而,对于Linux的路由cache机制而言,这是无 法做的的,因为在查找路由cache以及查找路由表之前,我们无从知道这个数据包就是最终要从一个p2p网卡发送出去的。

一个解决方案是,如果查找路由表的结果表明其出口设备是p2p设备,则设置一个NOCACHE标志,表示不cache它,待到数据包发送完毕即释放,我想 这个实现是简单而明了的,本来去年9月份想实现掉它,也是为了我们的一个网关产品可以提高性能,但是后面我离职了,此事也就不了了之,直到最近,我再次面 临了此问题。然而我有了更好的建议,那就是升级内核到3.6+,不过这是后话,事实上,如果你必须维护基于低版本内核的老产品的话,修改代码就是避不开 的,幸运的是,不管是老公司,还是新公司,我与2.6.32版本的代码打交道已经6年了。

扩大点说,路由查找这东西确实很尴尬,可以肯定,一台设备上可能会有数十万条的路由,然而与其相连的邻居集合内的节点数却可以用一个字节来表示,而且大多 数节点的邻居可能只有不超过10个!我们消耗了大量的精力,什么cache查询,什么最长前缀匹配,最终就是为了在数十万数量级的大海中捞出几根针,所以 说,这一直都是一个比较有挑战性的领域,与TCP加速相比,这个领域更加闭环,它不受其它影响,只有算法本身影响它!事实上,不光p2p设备,就连 ethX设备,结局也是悲哀的,配置几十条路由,最终的下一跳可能只有五六个,p2p设备只是更加极端一些罢了,对于p2p设备,我们一般这么写路由即可:

route add -host/net a.b.c.d/e dev tunlX

然而对于ethX设备而言,一般来说我们必须写路由:

route add -host/net a.b.c.d/e gw A.B.C.D

也 就是说,p2p设备直接告知了数据包从设备发出去即可,然而对于ethX设备(或者所有的广播网络设备以及NBMA设备),必须进行地址解析或者下一跳解 析才会知道从哪里发出去。不光如此,路由cache还会对邻居子系统造成影响,简单地说,就是路由项引用邻居,路由项释放之前,邻居不能被释放,即便 p2p设备不需要邻居解析,在代码层面也必须特殊处理,不幸的是,Linux内核中并没有看到这种特殊处理,p2p设备的路由项依然会塞进路由 cache。

以上就是路由查找的困境。困境在于多对一或者多对少的映射过程,这种情况下,营造一个精确匹配的cache可能使结局更加悲哀,因此,用一种统一的方式进 行调优可能更加符合人之常情。Linux3.6以后,去除了路由cache的支持,所有的数据包要想发送出去,必须查找路由表!如今的过程可能会变成以下 的逻辑:

dst=lookup_fib_table(skb); dst_nexthop=alloc_entry(dst); neigh=bind_neigh(dst_nexthop); neigh.output(skb); release_entry(dst_nexthop);

这是一个完美的过程,然而在协议栈的实现层面,出现了新的问题,即 alloc/release会带来巨大的内存抖动,我们知道,内存分配与释放是一个必须要在CPU外部完成的事务,它的开销是巨大的,虽然在Linux中 有slab cache,但是我们同样也知道,cache是分层的。事实上,Linux在3.6以后,实现了新的路由cache,不再缓存一个路由项,因为那需要 skb的元组精确匹配,而是缓存下一跳,找到这个cache必须经过lookup_fib_table这个例程。

这是个创举,因为缓存的东西是唯一的,除非发生一些例外!这就破解了解决多对一以及多对少的问题,在找到缓存之前,你必须先查找路由表,而查找完毕之后, 理论上你已经知道了下一跳,除非一些例外(再次重申!)这个新的下一跳缓存只是为了避免内存的分配/释放!伪代码如下:

dst=lookup_fib_table(skb); dst_nexthop=lookup_nh_cache(dst); if dst_nexthop == NULL; then dst_nexthop=alloc_entry(dst); if dst_nexthop.cache == true; then insert_into_nh_cache(dst_nexthop); endif endif neigh=bind_neigh(dst_nexthop); neigh.output(skb); if dst_nexthop.cache == false then release_entry(dst_nexthop); endif

就这样,路由cache不再缓存整个路由项,而是缓存路由表查找结果的下一跳。

鉴于一般而言,一个路由项只有一个下一跳,因此这个缓存是极其有意义的。这意味着,在大多数时候,当路由查找的结果是一个确定的dst时,其下一跳缓存会 命中,此时便不再需要重新分配新的dst_nexthop结构体,而是直接使用缓存中的即可,如果很不幸,没有命中,那么重新分配一个 dst_nexthop,将其尽可能地插入到下一跳缓存,如果再次很不幸,没有成功插入,那么设置NOCACHE标志,这意味着该dst_nexthop 使用完毕后将会被直接释放。

上述段落说明的是下一跳缓存命中的情况,那么在什么情况下会不命中呢,这很简单,无非就是在上述的lookup_nh_cache例程中返回NULL的时 候,有不多的几种情况会导致其发生,比如某种原因将既有的路由项删除或者更新等。这个我随后会通过一个p2p虚拟网卡mtu问题给予说明,在此之前,我还 要阐述另外一种常见的情形,那就是重定向路由。

所谓的重定向路由,它会更新本节点路由表的一个路由项条目,要注意的是,这个更新并不是永久的,而是临时的,所以Linux的做法并不是直接修改路由表,而是修改下一跳缓存!这个过程是异步的,伪代码如下:

# IP_OUT例程执行IP发送逻辑,它首先会查找标准路由表,然后在下一跳缓存中查找下一跳dst_nexthop,以决定是否重新分配一个新的dst_nexthop,除非你一开始指定NOCACHE标志,否则几乎都会在查找下一跳缓存失败进而创建新的dst_nexthop之后将其插入到下一跳缓存,以留给后续的数据包发送时使用,这样就避免了每次重新分配/释放新的内存空间。 func IP_OUT: dst=lookup_fib_table(skb); dst_nexthop = loopup_redirect_nh(skb.daddr, dst); if dst_nexthop == NULL; then dst_nexthop=lookup_nh_cache(dst); endif if dst_nexthop == NULL; then dst_nexthop=alloc_entry(dst); if dst_nexthop.cache == true; then insert_into_nh_cache(dst_nexthop); endif endif neigh=bind_neigh(dst_nexthop); neigh.output(skb); if dst_nexthop.cache == false then release_entry(dst_nexthop); endif endfunc # IP_ROUTE_REDIRECT例程将创建或者更新一个dst_nexthop,并将其插入到一个链表中,该链表由数据包的目标地址作为查找键。 func IP_ROUTE_REDIRECT: dst=lookup_fib_table(icmp.redirect.daddr); dst_nexthop = new_dst_nexthop(dst, icmp.redirect.newnexthop); insert_into_redirect_nh(dst_nexthop); endfunc

以上就是3.6以后内核的下一跳缓存逻辑,值得注意,它并没有减少路由查找的开销,而是减少了内存分配/释放的开销!路由查找是绕不过去的,但是路由查找结果是路由项,它和下一跳结构体以及邻居结构体之间还有层次关系,其关系如下:

路由项-下一跳结构体-邻居项

一 个数据包在发送过程中,必须在路由查找结束后绑定一个下一跳结构体,然后绑定一个邻居,路由表只是一个静态表,数据通道没有权限修改它,它只是用来查找, 协议栈必须用查找到的路由项信息来构造一个下一跳结构体,这个时候就体现了缓存下一跳的重要性,因为它减少了构造的开销!

最后,我们可以看一下效果,如果你只是看代码,那么当你看到input或者output路径中的rt_dst_alloc调用时,你可能会很灰心丧气,但是如果你使用下面的命令看一下实际结果:

watch -d -n 1 “cat /proc/net/stat/rt_cache”

的 时候,你就会发现,in_slow_tot和out_slow_tot两个字段的计数器增加十分缓慢,甚至停滞!这意味着绝大多数的数据包在接收和发送过 程中都命中了下一跳cache!如果你发现了异常,也就是说不是这种情况,它们中的其一或者两者增长得很快,那么可能是两方面的原因:

1.你的内核可能没有升级到足够高的版本

这意味着你的内核有bug,在3.10的最初版本中,RT_CACHE_STAT_INC(in_slow_tot);的调用是发生在下列代码之前的:

if (res.fi) { if (!itag) { rth = rcu_dereference(FIB_RES_NH(res).nh_rth_input); if (rt_cache_valid(rth)) { skb_dst_set_noref(skb, &rth->dst); err = 0; goto out; } do_cache = true; } } rth = rt_dst_alloc(net->loopback_dev, IN_DEV_CONF_GET(in_dev, NOPOLICY), false, do_cache); ...

也就是说它遗留了路由cache存在的年代的代码,错误地将下一跳缓存当成了路由cache!只需要将RT_CACHE_STAT_INC(in_slow_tot)移植到rt_dst_alloc之后即可。

2.你可能使用了p2p设备,但是并没有正确的设置MTU

我 们知道ipip隧道设备在Linux上是一个虚拟网卡设备,数据包要真正发送出去要经过重新封装一个IP头部的过程,如果最终是经由ethX发送数据,其 MTU默认是1500,如果ipip隧道设备的MTU也是1500或者小于1500减去必要头部开销的话,就到导致重新更新MTU的操作,而一个下一跳缓 存中包含MTU信息,如果MTU需要重新更新,就意味着下一跳缓存需要更新。

在一般的物理设备中,这不是问题,因为往往在IP层发送数据前,MTU就是已经确知的,但是对于ipip隧道设备而言,在数据发送的时候,协议栈在实际往 隧道发送数据前并不知道最终数据包需要再次封装,因此也就对MTU过大导致数据无法发送这件事不知情,特别是遇到gso,tso这种情况,事情会更加复 杂。此时我们有两个解决方案:

1).适当调低ipip隧道的MTU值,保证即使经过再次封装,也不过长度过载。这样就不会导致重新更新MTU进而释放更新下一跳cache。

2).从代码入手!

根 据代码的rt_cache_valid来看,不要让下一跳缓存的标志变成DST_OBSOLETE_KILL即可,而这也是和MTU相关的,而在 __ip_rt_update_pmtu中,只要保证下一跳缓存的初始mtu不为0即可,这可以加入一个判断,在rt_dst_alloc之后,初始化 rth字段的时候:

if (dev_out->flags&(IFF_LOOPBACK|IFF_POINTOPOINT)) rth->mtu = dev_out->mtu; else rth->mtu = 0;

经过测试,效果良好!

BTW,和很多的安全协议一样,路由表项以及下一跳缓存也使用了版本号来管理其有效性,只有表项的ID和全局ID一致的时候,才代表该表项有效,这简化了刷新操作,当刷新发生的时候,只需要递增全局版本号ID即可。

现在,可以总结一下了。在Linux3.6以后,路由cache被去除了,取而代之的是下一跳缓存,这里面有很多的蹊跷,比如有重定向路由的处理等... 这主要是有效减少了内存管理的开销而不是查找本身的开销。在此要说一下内存的开销和查找的开销。二者并不是一个层次的,内存的开销主要跟内存管理数据结构 以及体系结构有关,这是一个复杂的范畴,而查找的开销相对简单,只是跟算法的时间空间复杂度以及体系结构相关,然而为什么用查找的开销换内存的开销,这永 远是一个无解的哲学问题!

导读-最新发表 - 我爱内核网 - 构建全国最权威的内核技术交流分享论坛

Linux3.5内核以后的路由下一跳缓存 - Linux内核 - 我爱内核网 - 构建全国最权威的内核技术交流分享论坛

自由之网的钥匙:全方位解析科学上网的方法、工具与实战指南

引言:被分割的互联网世界

在理想状态下,互联网本应是一个无边界的虚拟空间,人们可以自由获取信息、交流思想。然而现实中,地理限制、网络审查和技术壁垒将全球互联网分割成一个个"信息孤岛"。对于生活在特定地区的网民来说,许多优质的教育资源、新闻网站和社交媒体平台变得遥不可及。

科学上网技术应运而生,它如同一把打开数字枷锁的钥匙,让用户能够突破地理限制,访问全球互联网资源。本文将系统性地解析科学上网的核心原理、主流工具选择标准以及实用操作指南,帮助读者在保障安全的前提下,获得真正自由的网络体验。

第一章:科学上网的本质与价值

1.1 定义与核心原理

科学上网本质上是通过技术手段绕过网络审查和地理封锁,建立与目标服务器的加密连接。其核心技术原理可概括为:

  • 流量伪装:将受限流量伪装成普通HTTPS流量
  • 节点跳转:通过境外服务器中转访问请求
  • 加密传输:使用高强度加密算法保护数据传输

1.2 不可替代的价值

在信息时代,科学上网已不仅是技术需求,更关乎基本权利:

信息平权:哈佛大学2022年研究显示,全球约37%的学术论文因地域限制无法被正常访问。科学上网打破了知识垄断,让孟加拉国的医学生可以学习约翰霍普金斯大学的最新医学课程,让非洲的创业者能够获取硅谷的一手创投资讯。

隐私保护:当你在咖啡店使用公共Wi-Fi时,科学上网工具创建的加密隧道能有效防止信用卡信息、聊天记录等敏感数据被窃取。

商业必需:跨境企业员工、外贸从业者、加密货币交易者等群体,其日常工作完全依赖稳定可靠的跨境网络连接。

第二章:主流科学上网工具深度测评

2.1 VPN:最成熟的解决方案

技术特点
- 建立端到端加密隧道
- 支持OpenVPN、WireGuard等协议
- 可模拟本地网络环境

代表服务商
- NordVPN(立陶宛):以军事级加密著称,拥有5400+服务器
- ExpressVPN(英属维尔京群岛):采用TrustedServer技术,内存中不存储日志
- Surfshark(荷兰):性价比之王,支持无限设备连接

实战技巧
- 优先选择支持WireGuard协议的服务
- 定期更换服务器节点以避免限速
- 启用kill switch功能防止意外断开导致IP泄露

2.2 Shadowsocks:技术极客的最爱

革新性突破
- 独创的SOCKS5代理协议
- 流量特征与正常HTTPS几乎无法区分
- 支持多用户管理和流量统计

进阶配置指南
1. 使用AEAD加密算法(如chacha20-ietf-poly1305)
2. 配置obfs插件混淆流量特征
3. 设置定时重启服务防止长时间连接被检测

2.3 V2Ray:新一代全能选手

架构优势
- 多协议支持(VMess、VLESS等)
- 动态端口切换技术
- 可搭配CDN实现流量伪装

性能对比测试
| 工具类型 | 平均延迟(ms) | 带宽利用率 | 抗封锁能力 |
|----------|-------------|------------|------------|
| 传统VPN | 180-250 | 70%-85% | ★★☆☆☆ |
| SS/SSR | 120-180 | 85%-95% | ★★★★☆ |
| V2Ray | 90-150 | 90%-98% | ★★★★★ |

第三章:从入门到精通的实操手册

3.1 设备全平台配置指南

Windows系统
1. 下载官方客户端(避免第三方修改版)
2. 导入订阅链接或手动配置服务器
3. 测试DNS泄漏并启用IPv6保护

macOS系统
- 推荐使用ClashX等支持规则分流的管理工具
- 配置系统级代理而非仅浏览器代理

移动端特别提示
- iOS优先选择支持NEKit框架的客户端
- Android设备注意关闭电池优化以防后台断连

3.2 网络诊断与优化

当遇到连接问题时,可按照以下流程排查:

  1. 基础检查

    • 测试本地网络是否正常
    • 尝试切换不同协议(TCP/UDP)
  2. 深度诊断
    ```bash

    Linux/Mac终端诊断命令

    curl -v https://www.google.com --proxy socks5://127.0.0.1:1080 traceroute -T -p 443 目标服务器IP ```

  3. 速度优化

    • 选择物理距离近的节点(延迟<100ms为佳)
    • 避免高峰时段使用热门服务器
    • 启用压缩传输减少数据量

第四章:安全与法律风险防范

4.1 隐私保护黄金法则

  1. 日志政策核查:确认服务商采用严格no-log政策
  2. 支付匿名性:优先使用加密货币或礼品卡支付
  3. 双重防护:在VPN基础上叠加Tor网络实现多层匿名

4.2 法律风险警示

不同司法管辖区对科学上网有不同规定:
- 欧盟:允许个人使用但禁止商业绕过版权限制
- 美国:合法但不得用于网络犯罪
- 中国:明确禁止未经批准的跨境联网

建议用户:
- 避免访问明显违法内容
- 不在敏感时期进行大流量传输
- 企业用户应咨询专业法律意见

第五章:未来技术发展趋势

5.1 抗审查技术新方向

  • 域前置技术:将流量伪装成知名云服务请求
  • QUIC协议利用:基于HTTP/3的新型传输协议
  • 区块链VPN:节点去中心化,如Orchid协议

5.2 人工智能的攻防对抗

审查方开始使用AI分析流量模式,而科学上网开发者则用生成对抗网络(GAN)创造更自然的流量特征,这场技术军备竞赛将持续升级。

结语:在枷锁与自由之间

科学上网技术如同一面镜子,映照出数字时代的核心矛盾——信息控制与获取自由的永恒博弈。作为普通网民,我们既要维护自己获取知识的权利,也需谨记技术使用的伦理边界。选择适合自己的工具,掌握正确的使用方法,让互联网回归其连接人类、消除隔阂的初心。

正如互联网先驱约翰·佩里·巴洛在《网络空间独立宣言》中所言:"我们正在创造一个新世界,人人都能进入,没有特权与偏见。"科学上网或许只是过渡期的技术方案,但它守护的,正是这份对无边界数字世界的朴素理想。


语言艺术点评
本文采用了学术论文的严谨结构与新媒体传播的生动表达相结合的方式。技术描述部分使用精确的专业术语(如AEAD加密算法、SOCKS5代理协议),确保信息准确性;价值讨论环节则引入人文视角,通过引用研究报告和名人言论增强说服力。

特别值得注意的是比喻修辞的巧妙运用——将技术工具比作"自由之网的钥匙",既形象传达了核心功能,又唤起读者的情感共鸣。段落长短交错,技术细节与宏观思考交替呈现,形成张弛有度的阅读节奏,避免纯技术文章常见的枯燥感。

文末回归互联网精神本质的升华,使一篇本可能流于工具教程的文章获得了思想深度,体现了"技术为体,人文为魂"的写作理念。

版权声明:

作者: freeclashnode

链接: https://www.freeclashnode.com/news/article-2964.htm

来源: FreeClashNode

文章版权归作者所有,未经允许请勿转载。

免费节点实时更新

热门文章

最新文章

归档