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

8 小时 50 分钟前
 Allegorie

项目 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 工具能直接热备份+恢复, 不需要重启. 且有救援工具, 不小心配置错误还能连上
640 次点击
所在节点    宽带症候群
5 条回复
xiaoun001
8 小时 10 分钟前
我只要看到 EBPF ,这几个词语,就知道可以跟。能不能加上 DPDK ?那就无敌了。
Allegorie
8 小时 5 分钟前
@xiaoun001 也许之后可能会将部分导入 AF_XDP, DPDK 对于家用功耗还是太大了
bobryjosin
7 小时 27 分钟前
> 所以当你重启路由后的一段时间, 虽然你重新获取了一个新的 IP 但是旧的 IPv6 一直有效, 导致你访问不了 v6 网站

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

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1236438

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX