V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
eh5
V2EX  ›  宽带症候群

einat-ebpf v0.1.0 发布,基于 eBPF 的 Full Cone NAT

  •  7
     
  •   eh5 · 25 天前 · 2523 次点击

    第一个版本,欢迎试用反馈!

    Full Cone 即 Endpoint-Independent Mapping 加 Endpoint-Independent Filtering 的 NAT 行为

    功能

    • NAPT44 和 NAPT66
    • 对 TCP ,UDP, ICMP 的 Full Cone NAT 行为
    • Hairpin, 允许在内部访问经过 NAT 的外部 IP + 端口目标

    是的,你没看错,不只是 UDP 的 Full Cone ,还支持对 TCP 和 ICMP 的 Full Cone ,基本上 match 现在三大运营商 IPv4 CGNAT 的行为了,所以你可以在运营商 Full Cone CGNAT 后面无限套娃 einat 。。

    而且这意味着你可以用类似 https://github.com/heiher/natmap 的工具开 TCP 服务。

    开发动机

    之前一直在用 https://github.com/fullcone-nat-nftables (由 https://github.com/Chion82/netfilter-full-cone-nat 启发) 的 patch, 但是它不仅需要 patch kernel, 还要 patch nftables 的用户前端程序和依赖库,实在太难维护了。而且自我开发 einat 前就已经没有维护了,而且现在 repo 也归档了。

    加上之前一直在各处看到 eBPF 的概念(我的第一印象是这东西怎么炒的这么厉害。。),再了解到确实可以在 TC hook 上实现 NAT ,索性从头写一个 NAT 顺便学习一下 eBPF ,前前后后完善了四个月终于完成了一个版本。

    现在已经在我的 R2S 上运行了几个月替代 Netfilter 的 masquerade 。

    在写 einat 的过程中我学到了很多,后续打算在我的博客发布几篇关于 einat 的文章和为什么 Netfilter masquerade 不是真的 Endpoint-Independent Mapping 。

    einat 主要是便利了基于 STUN 的 P2P 应用,但对于 BT tracker 模式这种依赖 UPnP IGD/PCP 的没什么用。所以在这个项目完善之后,我计划写一个基于 STUN 在 Full Cone 网络下工作的 PCP/NAT-PMP/UPnP 端口映射服务,主要是为了支持 BT tracker 模式。

    第 1 条附言  ·  25 天前

    使用方法

    使用 ip addr 找到出口网卡的名字比如 eth0, pppoe0, wan0

    简单情况用 -i/--ifname 指定出口网卡名字并在root权限下运行就行了,默认会 NAT 到出口地址的 20000-29999 的端口范围

    # einat -i eth0
    

    然后确保把 iptables/nftables 的 NAT 即 masquerade 规则删掉,否则没有效果,因为我们是在网卡层面做 NAT。

    第 2 条附言  ·  4 天前

    预定的文章终于写好了:

    此帖终结,其他关于 einat 或上文章的问题和讨论可以发在 einat-ebpf 项目的讨论区

    /cc @BoomMan @1423

    31 条回复    2024-04-09 20:17:38 +08:00
    Jirajine
        1
    Jirajine  
       25 天前
    这个看起来不错,但是不知道在和 netfilter/contrack/策略路由一起使用的时候会不会有未预期的行为,导致流量泄漏等问题。
    其实开一个用户态的实现了 fullcone 的 socks 代理,就可以满足很多应用的需求了。
    有状态的 nat6 应该是没什么使用场景的,ipv6 一般不用 nat ,即使需要 NPTv6 几乎总是最好的选项,除非你只有单个地址并且上游也是 fullcone 的。
    相比之下不如整一下 nat64 ,尝试把内网完全迁移到 ipv6 only ,可以极大的简化维护成本和 p2p 连通性问题。
    所有层 nat 全部都是 fullcone 的话,确实可以无限套娃,stun 打洞也能正常工作。但 upnp 那一套主动打洞协议却没有办法自动转发请求给上游,通过 stun 自己实现其中一部分协议应该是可行的。
    eh5
        2
    eh5  
    OP
       25 天前
    @Jirajine

    > 不知道在和 netfilter/contrack/策略路由一起使用的时候会不会有未预期的行为
    这个相当于在网卡端做了 NAT ,会对除了配置排除的目标地址无条件做 NAT 到选定的或配置的某一个外部地址,而且可选支持查路由表选定外部地址(即 prefsrc ),只要配置正确大概没什么影响,当然要具体情况具体分析。

    > 有状态的 nat6 应该是没什么使用场景的,ipv6 一般不用 nat ,即使需要 NPTv6 几乎总是最好的选项,除非你只有单个地址并且上游也是 fullcone 的。

    简单加个支持不难的,大部分代码都是复用的,而且正如你说的 NAT66 没什么用所以默认构建是关掉的。

    > 相比之下不如整一下 nat64

    我其实是 have NAT64 in mind 去开发的,见 https://github.com/EHfive/einat-ebpf/issues/3 ,但是这样 IP 包更改的内容就更多了,进一步增加 eBPF 程序复杂度,考虑到 NAT64 家庭应用实在没什么大的好处我没什么动机去开发,还得配个 NAT64 DNS server 。。
    yyzh
        3
    yyzh  
       25 天前
    所以有计划开发 openwrt 的 luci 面板以及加入 openwrt 的软件源吗?这玩意应该在 openwrt 上有比较高需求.
    另外你的端口映射软件有计划开发 openwrt 版的吗?现在在用 Natter 打洞但是没 openwrt 版的就很烦...
    eh5
        4
    eh5  
    OP
       25 天前
    @yyzh

    我个人没有计划,现在在路由器上用 NixOS 不怎么用 OpenWrt 了,但是如果这个软件获得了足够多的兴趣或许有人会写一个包和 luci 应用甚至最终合并到 OpenWrt 中(现阶段还太早了)。

    而且命令行很简单的,不一定需要 luci 面板。

    但是这个软件由于 Linux 内核对某些架构 eBPF JIT 实现缺失的原因,不支持在这些架构上运行,比如流行的 MIPS 。现在只测试过了在 x86_64 和 ARM64 上可以运行。而且对于 OpenWrt 你还得手动编译打开 eBPF 的内核编译选项才行。
    BoomMan
        5
    BoomMan  
       25 天前
    期待详细分享原理和使用场景及使用 case
    1423
        6
    1423  
       25 天前
    支持 op 做一期视频讲代码和使用, 开发和用户角度都涉及
    不管是 B 站还是油管这类视频都算优质内容

    而且也是趁热打铁
    maybeonly
        7
    maybeonly  
       25 天前

    有个问题是,有没有机会和现有的 ipt/nft 创建的 dnat 规则甚至 snat 结合使用呢?
    有考虑过自己搓一个,用 ipt/nft 写 snat ,对于匹配某些东西的 snat 到某些特定的源端口(范围),然后在出口侧抓住这些内核 nat 过的源端口,对与这些源端口相关的报文/连接/端口进行 full cone 的……不过不知道能实现到什么程度,以及代价到底怎么样
    再赞一遍。
    p.s. 用 netns 测试了 v4 ,好像没有理睬用 ip l s mtu 设置的 mtu ,设置为 1480 仍然是按照 1500 拆包的
    eh5
        8
    eh5  
    OP
       25 天前   ❤️ 1
    @maybeonly
    einat 支持指定 SNAT 源端口的范围,对于不在范围内的外部地址的出入包不做处理直接通过,所以只要做好端口区分混合 Netfilter 和 einat 没什么问题。你甚至可以 TCP 用 iptables/nftables masquerade 但 UDP 用 einat ,我在没完成 TCP NAT 功能时就是这么做的。

    > 在出口侧抓住这些内核 nat 过的源端口,对与这些源端口相关的报文/连接/端口进行 full cone

    这其实就是 netfilter-full-cone-nat 的思路,我最开始也是依赖 conntrack 做的, 但是这太 hacky 了而且维护状态很麻烦,并且 Netfilter 不是真的 “Endpoint-Independent Mapping” 所以会出现源端口复用的情况(即实际上是 “Address and Port-Dependent Mapping”)所以不可能实现真正的 Full Cone , 所以我重头开始实现了 NAT 。

    > ip l s mtu 设置的 mtu ,设置为 1480 仍然是按照 1500 拆包的

    你是说 IP fragmentation 么,很遗憾 eBPF 上实现不了,内核进来的包是多大 einat 处理过出去的包也多大。拆包应该是内核进行的。
    maybeonly
        9
    maybeonly  
       25 天前
    @eh5
    1. 既然想着共存了,改 conntrack 确实不是好事情,不过似乎可以(部分)绕过 conntrack ?
    当时考虑的是在入口和出口分别捕包,然后在出口处发现符合条件的报文后反查刚刚从入口抓过来的数据包,可能用到的匹配条件有:protocol+dst ip & port/id, length, 应该还有 ip 报文的 id 。
    抓到该端口相关的东西后续由 ebpf 完成 nat ,不再经过内核。
    2. 对于碎片,考虑发 icmpv6 type2 或者 icmp type3 回去?不确定能起多大作用。

    由于我太懒了,以上全部都停留在设想,具体能实现到什么程度,在真实网络环境中运行咋样,以及对性能的影响,也只能说停留在设想中了。。。
    p.s. ctrl+c 掉程序没有清理 tc 钩子,下次重启进程得手工删 tc 。。。

    再次感谢楼主。
    ysc3839
        10
    ysc3839  
       25 天前 via Android   ❤️ 1
    有一说一,netfilter-full-cone-nat 这个项目不需要 patch kernel ,可以在普通 kernel 的基础上编译出 kernel module 。只要 kernel 不大改,理论上是可以同一个编译脚本支持多个版本内核的,参见 https://github.com/ysc3839/openwrt-official-builds-fullcone
    不过还是免不了修改其他用户模式工具。
    mikewang
        11
    mikewang  
       25 天前
    赞,
    我之前一直没找到 Linux 环境上能模拟 CGNAT 行为的工具,用于测试 Natter 功能。
    这个对我来说十分有用 hhh
    eh5
        12
    eh5  
    OP
       25 天前
    @maybeonly
    > 对于碎片,考虑发 icmpv6 type2 或者 icmp type3 回去?不确定能起多大作用。

    这个只有改了包的大小超过 MTU 时需要发回去,比如 Cilium 的 NAT64 对超过 MTU 的包就是这样的,但 einat 没改理应不需要啊。。 为什么内核没有拆包我就不知道了

    > ctrl+c 掉程序没有清理 tc 钩子,下次重启进程得手工删 tc

    正常情况下 ctrl + c 是会清 bpf 程序再退出的,可以`bpftool prog` 看一下有没有 `egress_snat` 和 `ingress_rev_snat`, 但 qdisc clsact 确实没删但也没什么大问题(主要是懒得查 qdisc 占用情况了,也不能全部删掉。。)
    Love4Taylor
        13
    Love4Taylor  
       25 天前
    @ysc3839 真一路到底的话直接 hardcode 内核 patch 里的参数,本质上给用户端工具打 patch 就是让其可以传参来开启功能吧,缺点就是用户没法后悔关 fullcone

    0001 patch 走 iptables 经测试 tailscale 以及 docker 可以 nat1 ,0002 没试。
    https://gist.github.com/love4taylor/a56985ecde4f16bab2cf54ee1c6a0c32
    maybeonly
        14
    maybeonly  
       25 天前
    @eh5
    > 这个只有改了包的大小超过 MTU 时需要发回去,比如 Cilium 的 NAT64 对超过 MTU 的包就是这样的,但 einat 没改理应不需要啊。。 为什么内核没有拆包我就不知道了
    理论上吓一跳链路比包“窄”就需要。虽然对于 ipv4 ,我是没见过哪个路由器是不分片而回 icmp 的(除非设置了 df )

    > 正常情况下 ctrl + c 是会清 bpf 程序再退出的,可以`bpftool prog` 看一下有没有 `egress_snat` 和 `ingress_rev_snat`, 但 qdisc clsact 确实没删但也没什么大问题(主要是懒得查 qdisc 占用情况了,也不能全部删掉。。)
    没有了,只是再启动会报个 warning ,并不影响正常工作的。
    WARN einat: libbpf: Kernel error message: Exclusivity flag on, cannot modify
    s82kd92l
        15
    s82kd92l  
       25 天前
    赞!可以提供 mips 版的二进制包么?
    eh5
        16
    eh5  
    OP
       25 天前
    @s82kd92l
    MIPS 不支持,看上面关于 OpenWrt 的回复 https://www.v2ex.com/t/1029886#r_14545955
    eh5
        17
    eh5  
    OP
       25 天前
    @maybeonly 哦,这个确实,那目前要改 MTU 只能把相关转发的网卡也改了,避免在 einat 处理拆包,不过 BPF 里或许可以通过 `BPF_FIB_LKUP_RET_FRAG_NEEDED` 知道 MTU 信息从而发回 PMTU 错误包,不过这又是一大工程了。。得在 BPF 里构建一个包。。
    Jirajine
        18
    Jirajine  
       24 天前
    @eh5 #2 常见的家庭使用场景:
    通过策略路由把在 netfilter 中根据元数据打了不同标记的包路由到不同的外部接口。
    通过 ct 把来自外部接口发起的入站连接打标把相应的回复包从它们的来源接口发回去。
    通过 ct 把来自某个 mac 地址的包打标从而能够在路由选择之前匹配到将要发送到该地址的包。

    通过 nat64 能部署纯 ipv6 内网最大的好处就是简化双栈网络的复杂度,防火墙/路由/地址规则不用每个主机都重复两遍,双栈选择可以直接在 dns64 服务器中配置,不用为每个应用每个设备都单独配置,用容器跑个 p2p 应用不用修改镜像的 gai.conf (非 glibc 的应用也不一定遵守),有些系统(如 android )直接硬编码 prefer ipv6 无法修改。
    常见的 dnsmasq/adguardhome/pi-hole/dnscrypt-proxy 等转发器都内置了 dns64 的功能,不过国内的公共 dns 没一个支持 dns64 的。
    eh5
        19
    eh5  
    OP
       24 天前
    @Jirajine

    https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks 中的图表为示意图,
    einat 和 Netfilter DNAT/SNAT 的区别是前者 NAT 发生在出口网卡的 ingress 和 egress ,后者发生在 Prerouting 和 Postrouting ,所以只要你的规则本来就在这之间不会有区别。当然如果你配置了 hairpin 路由配置那对你的策略路由或许会有影响,具体路由见配置 hairpin 后的 `ip rule`,另见 https://github.com/EHfive/einat-ebpf/issues/4#issuecomment-2001996895

    > 通过 nat64 能部署纯 ipv6 内网最大的好处就是简化双栈网络的复杂度,防火墙/路由/地址规则不用每个主机都重复两遍

    NAT64 对于业务组网确实有好处,但应用必须依赖 DNS64 ,对于有限制的业务网络当然没什么问题。 然而如果应用内附了 L3 IPv4 信息,比如以 IPv4 地址作为 Host 的 HTTP 服务(对于家庭/办公室网络是很常见的场景),那为了访问这些 IPv4-only 的应用必然得在终端设备配置静态 NAT46 ,而且也不可能覆盖所有非通用设备,那最后还是需要冗余的双栈网络覆盖所有终端设备。你说的简化网络复杂度和方便配置规则的好处也就不成立了。
    Jirajine
        20
    Jirajine  
       24 天前
    @eh5 #19 如果我理解的不错的话,把这个 ebpf 挂载到网卡上之后,对网络栈的其他部分而言完全透明,就好像根本没有使用 nat ,来自的内网地址的包会被公网的路由器发回来一样。
    这样的话 netfilter/策略路由行为应该和分配到一个公网 ipv4 网段的路由器一样,你自己维护自己的映射表,和内核的 conntrack 也互不影响,多个外部网卡运行多个实例进行策略路由的情况下应该也不会有问题。
    不过这个 harpin 看起来有点太 hacky ,这样做肯定会和很多东西冲突的,比如本地监听的程序和 netfilter dnat 。对于 masquarade 而言根本不需要做这种事情,没有哪个 p2p 的应用需要从内网访问映射的端口而不是直接连接。这种事情就交给 netfilter dnat 做,只要端口不冲突应该就不会冲突,hairpin 需要进行 snat ,会让应用丢失掉源地址信息,只有特别需要的场景才会专门配置 snat ,或者在用户态使用 proxy protocol 的转发来保留源地址信息。

    关于 nat64 ,最主要是能够控制 android 这种不允许用户控制、且 ipv6 实现不完整的设备在双栈环境下的行为。
    纯 ipv6 网络就不要部署以 ipv4 地址作为 host 的 http 服务啊,可以用 hostname ( dns/dhcp 自动分配和解析),或者配置一个简单好输入的 ULA 地址,实在不行可以手动为需要的设备配置静态 ipv4 地址(甚至 dhcp 也行),但是不要下发路由和网关,这样依然是一个纯 ipv6 网络。访问外部的 ipv4 http 服务也不需要专门配置,现代系统会自己自动进行本地 464XLAT ,虽然多了层套娃,但这种转换对网络侧透明,不会增加复杂度。
    还有一个 nat64 与打洞相关的场景,一个 p2p 应用可以监听一个 GUA ipv6 的 socket ,然后同时接收来自打洞映射的 ipv4 入站和 GUA ipv6 入站,也就是纯 ipv6 下的 p2p 应用可以无缝的、透明的和纯 ipv4 下的 p2p 应用互联。无论 ipv4 的打洞是否成功,该应用都可以接收到入站,并且在不同的网络环境不需要单独处理或配置双栈。
    eh5
        21
    eh5  
    OP
       24 天前
    @Jirajine

    > 对于 masquarade 而言根本不需要做这种事情,没有哪个 p2p 的应用需要从内网访问映射的端口而不是直接连接

    RFC 4787 ,REQ-9: A NAT MUST support "Hairpinning"

    需要,而且很需要,如果没有 Hairpin ,你自己的外部地址+端口外部可以访问但内部不能访问不是很奇怪么,而且这个内部包括整个 NAT 后面的网络。没有 Hairpin ,内部网络间的 P2P 就无法联通。

    > 这样做肯定会和很多东西冲突的,比如本地监听的程序和 netfilter dnat

    你有点想当然了,hairpin 的流量最终是会从网卡返回来的,对于需要 NAT 的流量会进行 NAT ,不需要的流量会最终 loopback 回来,这个路由规则相当于在正常的 (lan -> local) 包流中加入 einat (lan -> einat > local),并不会有什么所谓的冲突。

    > 网络就不要部署以 ipv4 地址作为 host 的 http 服务啊

    这不是你可以控制的,没有域名使用 IPv4 作为地址的公共网站还是有的,用户也有需求访问这些网站。而且可以嵌入 L3 IPv4 地址的协议也不止 HTTP ,比如 BT 。对于家庭/办公室网络这些都是刚需。

    > 现代系统会自己自动进行本地 464XLAT

    我不知道有哪个 Linux 发行版默认配置了这个,而且家庭用户的设备也不一定是高集成度的”现代系统“。

    关于 NAT64 你单方面的阐述它对你限制性应用场景下方便网络配置的必要性就到此为止吧,这差不多跑题了,建议另开一主题先让大家达成共识。

    而且你我都不在一个频道上,我的论点是 NAT64 下的 IPv6-only 对于家庭用户来说为了配置它所付出的并不能得到相比传统双栈网络在网络连通性、速度方面的提升,作为家庭用户我个人并没有动机去使用它并花费精力实现它,所以对于在 einat 中实现 NAT64 的优先级为低。

    我对 einat 预想的使用场景充其量是家庭和办公室网络,而且非图灵完备的 eBPF 程序能实现的功能是有限制的,对于其他场景和更高级的功能建议使用企业级 NAT 设备。。
    eh5
        22
    eh5  
    OP
       24 天前
    @Jirajine

    > 如果我理解的不错的话,把这个 ebpf 挂载到网卡上之后,对网络栈的其他部分而言完全透明,就好像根本没有使用 nat ,来自的内网地址的包会被公网的路由器发回来一样。

    是的,我应该提一嘴 einat 是从头开始写的不依赖 conntrack 的独立 NAT 实现,einat 的 README 里也有稍微提到,https://github.com/EHfive/einat-ebpf?tab=readme-ov-file#alternatives

    最开始的版本的确是类似一些 eBFP 负载均衡应用通过维护 Netfilter conntrack 实现的,但是 eBFP 无法控制初始 conntrack 的源端口分配所以并不能把 “Address and Port-Dependent Mapping” 的 conntrack 系统限制为 “Endpoint-Independent Mapping”,所以初始版本是一个失败的实现,见 https://github.com/EHfive/einat-ebpf/tree/legacy 。于是就决定从头写一个独立的 NAT 实现。
    maybeonly
        23
    maybeonly  
       24 天前
    @eh5
    > “和很多东西冲突”

    也曾考虑过这方面的问题,在真实部署中会很依赖端口选择(配置)
    确实,代码本身不会利用自己已经用过的 snat 后的源端口,但是如果这些端口和其他 dnat 规则冲突,抑或被其他程序占用呢?运营商的 cgnat 没有这个问题,毕竟人家的 ip 就是专供 nat 用的。家用的话,可能不得不特别小心 dnat 的选择,以及用 ip_local_port_range 隔离了。然后发现 dnat 搭配的 hairpin 怎么办? emmmm 。。。

    当时自己考虑的结果是,不得不和 conntrack 做某种程度的“交易”才能解决这个问题。
    交给 conntrack 选 snat 后的源端口就没这个问题,只要识别到这个内部 ip:port 和外部 ip:port 的组合,后续不走 conntrack 按照 fullcone 的实现就好了。不仅能指定源端口,也可以实现比如针对某个 ip 实现 fullcone ,etc 。

    同时被解决的另一个问题是,什么都没配置的话可以继续走系统的 conntrack ,只有配置了命中了正确的端口范围才能 fullcone 。在实践上也会是相对比较安全的。

    然而咱终究是懒得 1b ,对勤奋的楼主表示深深的敬意。
    eh5
        24
    eh5  
    OP
       24 天前
    > 但是如果这些端口和其他 dnat 规则冲突,抑或被其他程序占用呢
    > 可能不得不特别小心 dnat 的选择,以及用 ip_local_port_range 隔离了

    嗯,对于本来预想接受初始入站连接的端口(不管是本机的监听服务还是对外部地址端口的 DNAT )是应该被排除在 einat 的 NAT 端口范围外。我当初也是预想到了这种情况所以在 einat 中实现了端口范围限定,并且对 TCP 和 UDP 以 20000-29999 为默认端口范围从而隔离可能常用的低端口和 ip_local_port_range (默认 32768-61000 )。

    einat 对于范围内的端口则是严格执行 EIM + EIF ,其中重要的一点是只允许在有从内部发出的初始出站连接后才能打开相应的源端口接受后续其他入站连接。而且即使对于以网卡外部地址+范围内端口为 source 的初始出站连接也是如此(即范围内的端口有可能被重映射,比如 egress: 20000 -> 20001, ingress: 20001 -> 20000 ),但因为 einat 如你所说是透明的,这其实和其他以私有地址为来源的出战连接没多少不同,见 https://github.com/EHfive/einat-ebpf/blob/9e6f8e6720c7244f40693e2bd119d268b855afd5/src/bpf/einat.bpf.c#L1879-L1892

    PS:话说我应该在博客里写这些的。。
    eh5
        25
    eh5  
    OP
       24 天前
    @maybeonly
    > 但因为 einat 如你所说是透明的
    *但因为 einat 如 @Jirajine 所说是透明的

    抱歉,刚起床迷糊把你看成他了。。
    Jirajine
        26
    Jirajine  
       23 天前
    @eh5 你说的对,内部网络之间(非同一子网)的 p2p 应用确实需要最外层的 nat 网关支持 hairpin ,这是我之前忽略了的情况。但 nat 就是这样的 hack ,开启了 hairpin 会 break 某些应用,关闭了 hairpin 会 break 另一些应用。是否需要 hairpin 取决于具体需求,比如现在大部分家庭用户都是无公网 ipv4 (意味着 hairpin 发生在最外层的运营商 cgnat 上)+公网 ipv6 (对任何 sane 的 p2p 应用都是首选),实现 hairpin 的意义就不是那么大。
    hairpin 最大的问题是 snat ,snat 会丢失源地址信息,当你收到一个入站包的时候,你必须 remotely 知道你要转发的目的主机到源主机之间的路由是否还经过你,才能决定是否进行 snat ,这很难正确的实现,你只能假定一些简单的网络拓扑下的情况。
    具体到通过策略路由把目的为本机的包强行发到外部接口上,这已经 break 了其他所有人预期的行为,而你无法提前知道和测试所有的使用场景。比如本机端口冲突、多实例多地址个外部接口和类型( eth/pppoe/wireguard/其他基于 tun 的代理程序)的动态路由场景,动态 ipv4/ipv6 地址。并且这会让所有目的的为该地址的入站包全部发送到外部网卡再转发回来,那么本来从内网入站的包源网卡成了外部网卡(并且经过两遍 netfilter ),正常系统都会默认的允许内网入站、禁止外网入站的防火墙规则也就 break 了。更不用说如果内网接口都是 virtio 的虚拟网卡,这种转发会非常严重的降低性能(这还没有考虑 ebpf 本身的开销,在嵌入式设备上应该也是不可忽略的)。
    我觉得既然因为网卡上的 ebpf 无法捕获发往本机的包,那干脆直接让用户态的服务 reserve 所有活跃记录中的端口,然后对 hairpin 的请求直接在用户态转发,这样也能解决端口冲突问题(其他程序没有办法得知 nat 使用了哪些端口)。

    NAT64 并不是限制性应用场景,646xlat 不同于 nat64 ,它是无状态的静态重写。对于支持 464xlat 的终端,并不会 break 任何除了 nat44 已经 break 的应用,唯一的 regression 就是网关有没有实现 fullcone 。当我说所有现代设备,我指的是所有面向消费者的设备,所有可以插 sim 卡的系统都必须实现 464xlat (因为很多运营商的移动数据已经是纯 ipv6 了),包括 windows/android/ios ,linux 没有把它做到开箱即用确实 behind 了。对于不支持的设备,它们不太可能依赖内嵌 ipv4 的应用,这些设备要么压根根本不需要公网联通,要么可以在纯 ipv6 环境工作。
    如果你考虑连通性方面的好处,那么毫无疑问增加 ipv6 的 adoption 是对每一个 p2p 用户都有好处的。ipv6 现在的状态已经达到了只要你是接入 isp 的就已经普及了,没有的都是本地网络不支持(云厂商/企业/家庭网络用户自己),去搜一下会发现用户自己禁用 ipv6 的原因,像什么卡/打不开网页禁用 ipv6 就好了,都是因为复杂度或某些系统实现的问题(没错,又是 android ),而这些问题在纯 ipv6 环境下却可以解决和避免。如果以后因为缺少一个 fullcone 的 nat64 实现(上游那些生活在公网 ip 非常充裕的国家的人不太可能去支持)而 block ipv6 的 adoption 的话,对 p2p 应用绝对是不好的,用户希望能够同时拥有 ipv4 fullcone 和 ipv6 gua 的连通性。从端到端连通性的角度来看,fullcone 的 nat64 比 nat44 更 canonical ,毕竟打洞总是 hack ,ipv6 才是“正确”。

    不过实现 nat64 确实会引入额外的复杂度,虽然在 nat44 的基础上没有额外的状态只需要一些静态重写,不熟悉 ebpf ,但是现在只有重写地址就要两千行了(可能大部分是维护状态?),再加上构造包可能会不容易,我记得几年前还看到有新闻把 rust 编译到 ebpf ,现在也没发展了。
    上面回复有提到需要构造 icmp 报文返回 mtu 问题,这应该搞错了吧,分片或者返回 icmp 应该是网络栈或者网卡的事,nat 只是重写包的地址,甚至是否 EIF 都是防火墙配置的,和 nat 有啥关系。
    Jirajine
        27
    Jirajine  
       23 天前
    @eh5 说道性能,还有一个值得考虑的问题。现在常用的家用嵌入式路由器的 cpu 根本无法支持高带宽场景下的 nat ,而是 offload 到专用硬件处理。考虑到这种硬件应该是负责重写包而不是负责连接追踪,实现支持 hw offload 的 fullcone nat 应该是可能的,只不过能否通过 ebpf 就不知道了。不然的话,很可能受限于嵌入式设备的性能而让用户的带宽严重降低。
    s82kd92l
        28
    s82kd92l  
       23 天前
    其实 linux 内核里 openvswitch 社区热度还蛮高的,xdp dpdk bpf 甚至是 p4 后端都有人做实现。fullcone nat 如果用 ovs 或者 openflow 做用户态实现可能适用面更广呢,性能应该不输直接 bpf
    eh5
        29
    eh5  
    OP
       23 天前
    @Jirajine

    > 具体到通过策略路由把目的为本机的包强行发到外部接口上,这已经 break 了其他所有人预期的行为

    所以你不是已经知道了么?默认的规则已经覆盖了最常见的场景了,你对于自己的网络有特殊配置完全可以关闭 hairpin 或者定制你自己的路由规则不是么,请注意这个主题的内容是关于 einat ,如果你对于任何 einat 的实现有建议意见提升方法的话请发 issue 。

    > 这种转发会非常严重的降低性能(这还没有考虑 ebpf 本身的开销,在嵌入式设备上应该也是不可忽略的)

    请你先 profile 一下给出比较数据,或者给出 source 。

    > NAT64

    关于你对 NAT64 的话题已经 off-topic 了,我也不会再重复我之前的观点(当然你也没有看到我的侧重点而是单方面的输出),请单发主题或者在 https://github.com/EHfive/einat-ebpf/issues/3 下给出评论。

    > 说道性能,在常用的家用嵌入式路由器的 cpu 根本无法支持高带宽场景下的 nat ,而是 offload 到专用硬件处理

    off-topic ,偷换概念,比较的对象应该是 Netfilter conntrack SNAT/DNAT 和 einat 而不是 NAT offloading 和 einat 。

    鉴于你一直无视主题单方面输出观点,我在此主题不会再回复你的评论。
    eh5
        30
    eh5  
    OP
       23 天前
    @s82kd92l
    不是很了解 Open vSwitch 和 OpenFlow ,但粗略的看了下似乎是基于匹配规则进行包的转发,而且它的 NAT 似乎也是基于 Netfilter conntrack 的,见 https://ovs-istokes.readthedocs.io/en/latest/tutorials/ovs-conntrack.html

    但要实现 Endpoint-Independent Mapping 还是需要实时地在包解析阶段就查询更新 mapping 表。如果不使用 conntrack 通过用户态的监控并(滞后地?)添加映射规则那实时性肯定不够的。而如果使用 conntrack 那和我上面所说在 eBPF 中动态维护 conntrack 表没有区别,你只能去控制 filtering 行为而无法控制 mapping 行为,最终结果还是会回落到 Netfilter conntrack 系统的 “Address and Port-Dependent Mapping” 行为。

    如果 会有某一天 kernel 实现了 EIM + EIF ,那也只会是独立于 conntrack 的另外一套系统,因为 conntrack 本来就是 Address and Port-Dependent 的。
    Jirajine
        31
    Jirajine  
       21 天前
    @eh5 你认为 nat64 是限制性应用场景,我解释 xlat 能兼容所有 nat44 的场景;你认为 nat64 对家庭用户比双栈网络没有连通性和速度方面的提升,我告诉你事实上是有,很多用户双栈网络的连通性和速度还不如 ipv4 only ,他们关于禁用 ipv6 的帖子搜索引擎一搜一大把。对了,作为一个 side effect ,nat64 还能让你不用为了实现 hairpin 而把所有内网入站连接都从外部网卡绕一圈,你认为这种做法没问题那就没问题吧,记得告诉你的用户在防火墙中允许来自公网接口的入站连接以及相应的 security implication 。

    当然你认为以上都是 off topic ,“没有看到你的侧重点单方面输出”,没有问题,是否实现或以什么优先级实现 nat64 是你的自由,我并没有要求你做什么,其他问题跟用户说去吧,本帖不用再回复。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2190 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 10:26 · PVG 18:26 · LAX 03:26 · JFK 06:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.