Allegorie

Landscape 一个从零实现的软路由系统 - Rust + eBPF

  •  1
     
  •   Allegorie · 1 day ago · 1509 views

    项目 GITHUB: https://github.com/ThisSeanZhang/landscape

    为什么做 Landscape

    想法出现是在一次 OpenWrt 崩溃。我明白崩溃并不是 OP 自身的问题.

    应该是我选择的插件之间出现了兼容性的问题, 而每次修改 OP 的配置总是让我觉得战战兢兢, 要么是觉得配置好像没有生效, 要么太多的输入框和按钮展示在我面前. 学习 OpenWrt 的成本让我觉得很大, 所以我就在想是否能在通用发行版 Linux 的基础上, 实现一个自己的路由?
    而那时正在学习 Rust (入门第 N 次) 和 eBPF, 手上正握着把锤子, 想找个东西敲打一番, 于是我的计划就开始了.

    开始对于我自己也就这几个需求:

    1. 怎么彻底的抑制 PCDN 偷上行
    2. 怎么方便将内网设备更新 DDNS.
    3. 不同的网站针对不同的设备使用不同的出口进行访问(当时还不知道有那么多丰富的插件).
    4. 不要每次都刷整个系统, 程序可以直接升级. 并且最好是只导出一份文件就够了.

    简单规划之后, 便开始进行实施了

    NAT 的设计

    NAT 能力演示: https://www.bilibili.com/video/BV1b8Tn6ZEaz
    在使用组网软件的时候, 就看到了 tailscale 的一篇文章. 是关于 tailscale 是怎么进行 P2P 打洞的, 这给予了我灵感.
    简要的流程是, 当两个设备要进行穿透时, 需要先向一台 STUN 服务器进行请求, 告知自己的地址. 服务器向双方告知对方的地址, 双方开始尝试互联, 所以可以利用这个机制, 如果要进行强力阻断的, 默认端口不可复用即可.

    比如在连接存活期间:
    客户端 ASTUN 服务器 B ✅ 允许
    STUN 服务器 B路由 A' ✅ 允许, 然后路由转为 B → A
    客户端 A另一个客户端 C ❌ 直接丢弃(不会按照 NAT4 那样创建新映射)
    另一个客户端 C路由 A' ❌ 丢弃

    所以将会被迫回退中继模式.

    而允许进行 P2P 的只要放行即可, 行为与全锥一致, 这已经在一年多的使用中已被证实是可行的, 且对日常毫无影响.

    而另外一个大头是 IPv6 的支持

    在 op 上的 IPv6 我总觉得用起来很奇怪, 当我自己实现的时候我才明白具体的原因是什么.
    首先是 IPv6 首选时长和上游给的时间一致的问题, 而 DHCPv6 虽然设计了 reconfigure 机制, 但是现在的大部分客户端都没支持.
    所以当你重启路由后的一段时间, 虽然你重新获取了一个新的 IP 但是旧的 IPv6 一直有效, 导致你访问不了 v6 网站.

    不过 landscape 中额外考虑了另一个场景, 也就是 IPv6 NPT, 不是将 IPv6 使用 nat66 的方式进行 IP 映射, 而是当有多网口时, 即使内网设备获得的是 A 网口的 IP, 流量实际也能从 B 网卡发出, 并自动能替换成 B 网卡获得的前缀信息.

    还有 op 上不总是能固定后缀, 而 landscape 中不仅能固定后缀还能配合 DDNS 直接将 LAN 获得的 IP 直接写入运营商的 DNS 记录.

    不同设备应用不同的规则

    分流能力演示: https://www.bilibili.com/video/BV1Wy26BiEJW/
    现有的科学上网分流, 都是对本地局域网内的所有设备应用, 但是我不想所有的设备都是走同一套规则.
    比如, 开发主机是全局的. IoT 设备是全部走直连. 某些设备只有部分网站使用科学.
    并且虽然只配置了一个 DNS 上游. 但是依据不同的 DNS 请求要与出口一致, 且这样天然就能拿到最终落地所在的 CDN 地址.
    有看到部分人称为 DNS 跟踪, 我觉得称为 DNS 出口亲和可能更合适?

    假设你想要使用某个新协议, 你需要等待某些作者进行合入现有的分流程序. 或者等待这个协议作者实现一套完整的分流.
    landscape 将容器出口也作为与 WAN 网卡同级别的一等公民. 其中使用 tproxy 进行解耦. 只要容器内运行支持 tproxy 的程序即可进行流量处理.
    多个容器间直接切换也是无缝的, 因为在 landscape 已经做好了分流, 容器内只需设置全局的配置就行.

    且不同规则组的 DNS 缓存是隔离的. 不会因为融合了不同的策略, 导致 DNS 缓存相互覆盖.
    比如:
    A 网站在 X 策略组中是直连的 -> 得到 Ax IP. -> X 策略组缓存
    A 网站在 Y 策略组中是由容器处理 -> DNS 请求通过容器 得到 Ay IP. -> Y 策略组缓存

    但是对于 Anycast IP 是怎么处理呢? 目前你可以通过在容器中部署支持 Fake IP 的组件. 然后将部分网站的 DNS 上游指向这个容器即可
    比如:
    A 网站在 X 策略组中是直连的 -> 得到 Ax real IP.
    B 网站在 X 策略组中是指向 容器 I 的 -> 得到 Bx fake IP.
    C 网站在 X 策略组中是指向 容器 J 的 -> 得到 Cx real IP.

    可以做到只有部分容易混淆的访问使用 FakeIP

    只将必须的流量转到容器还能额外获得一个优点, 你的科学工具不必再处理直连流量. 更不容易炸, 且炸了也只影响你的海淘. 不影响你的直连. 而直连就和正常的上网一样, 所以延迟相比过科学会再低一点.

    其余一些特点

    安装: https://www.bilibili.com/video/BV1aZ8w6TEY9/

    1. 前后端完全分离, 所以你可以通过 API 控制所有 UI 上可以控制的所有行为, 也意味着你可以实现一套自己的 UI
    2. 可将所有的配置导出为一份文件, 并通过这一份文件恢复整个路由的配置
    3. 有 tui 工具能直接热备份+恢复, 不需要重启. 且有救援工具, 不小心配置错误还能连上
    12 replies    2026-08-23 23:37:40 +08:00
    xiaoun001
        1
    xiaoun001  
       1 day ago
    我只要看到 EBPF ,这几个词语,就知道可以跟。能不能加上 DPDK ?那就无敌了。
    Allegorie
        2
    Allegorie  
    OP
       1 day ago
    @xiaoun001 也许之后可能会将部分导入 AF_XDP, DPDK 对于家用功耗还是太大了
    bobryjosin
        3
    bobryjosin  
       1 day ago
    > 所以当你重启路由后的一段时间, 虽然你重新获取了一个新的 IP 但是旧的 IPv6 一直有效, 导致你访问不了 v6 网站

    现在应该很少设备会专门用 dhcpv6 来分配地址,如果使用 slaac ,其实是 preferred lifetime 参数设置的有问题,导致客户端不抛弃旧前缀,RA 报文里面把旧前缀的 preferred lifetime 设置为 0 就行了,其实也不需要 nat66 ,现代客户端收到 pref. time 0s 就会自动丢弃旧前缀。
    Allegorie
        4
    Allegorie  
    OP
       1 day ago
    @bobryjosin 是的, 现有的实现中会在前缀过期时发送 preferred lifetime = 0 的报文. 只是说有时候意外路由重启会导致这样的情况.
    slowman
        5
    slowman  
       1 day ago
    所以怎么彻底的抑制 PCDN 偷上行
    freebsdjlu
        6
    freebsdjlu  
       1 day ago
    @Allegorie #2

    家用根本用不到
    Allegorie
        7
    Allegorie  
    OP
       20h 28m ago
    @slowman
    因为 PCDN 实际上有点类似组网,本质上是让你的设备和另一个用户建立 P2P 连接,让你的设备通过 P2P 给其他用户提供内容,也就是利用你的上行带宽给别人下载。

    所以前面 NAT 那部分说的就是这个功能:你可以正常和 PCDN 的服务器建立连接,但服务器建立的 NAT 映射不能被另一个用户拿来直接复用。

    这样即使对方是 NAT1 ,也无法直接和你的设备建立 P2P 数据通道。PCDN 的调度服务器发现这个节点无法作为 Peer 后,就只能换其他节点,或者回退到中继/服务器分发。

    这样达到的效果就是:你仍然可以正常从 CDN 下载,但你的设备不能被 PCDN 当作其他用户的 P2P 下载节点。
    luoshengdu
        8
    luoshengdu  
       20h 27m ago
    @slowman 在 nat 实现上不让他们协商打洞***客户端 A → 另一个客户端 C ❌ 直接丢弃(不会按照 NAT4 那样创建新映射)
    另一个客户端 C → 路由 A' ❌ 丢弃***
    Allegorie
        9
    Allegorie  
    OP
       20h 1m ago
    @freebsdjlu 是的, 对于大多数的家用来说. 可能挂载在 TC 上的 eBPF 就足够了
    rulagiti
        10
    rulagiti  
       18h 16m ago
    看上去好复杂,还是继续 iptables
    SAGAN
        11
    SAGAN  
       11h 31m ago
    我没理解你是怎么实现不同网站 & 不同设备使用不同出口的。设备分流很容易,但是根据域名分流是怎么弄的,还是传统的 DNS fake ip 方式吗
    Allegorie
        12
    Allegorie  
    OP
       8h 11m ago
    @SAGAN 将 DNS 后的 real IP 记录, 并打上标记, 并在一个规则组中生效. 待规则组中的设备访问这个 IP 时, 按照标记转发到容器中.
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   1226 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 23:49 · PVG 07:49 · LAX 16:49 · JFK 19:49
    ♥ Do have faith in what you're doing.