跟风贴自家软路由实现

10次阅读

共计 4797 个字符,预计需要花费 12 分钟才能阅读完成。

设计目标

  • 为了玩得开心。
  • 并不是追求极致性能。实际上用 n100 跑,常年 CPU idle 90%+。
  • 模块化,面向数据包和连接,全真 IP
  • 结构复杂但是条理清晰,拓展性好。轻松适配各种形式的 VPN。

设计总则

  • 基础 OS 使用 Rocky linux 9。为什么使用 RH 系,问就是历史原因。
  • 从 DD-WRT 上迁移出来大概是 2016 年,逐步发展成现在的样子。
  • 使用 netns 创建各自相对独立的网络命名空间
  • 各 netns 分别分配功能、配置 ip,用虚拟网卡、虚拟网桥互联
  • 虚拟网卡(直接接入 LAN 的除外)统一分配静态 MAC 地址,由 IP 地址生成。MAC 匹配被大量应用
  • 静态路由宣告,因为都在同一台物理设备,直接使用脚本在必要的 netns 内执行 ip route add。
  • 总体使用 bash 编写,部分模块使用 c++

    • 最初是感觉容易移植来着
    • 实际上的好处是脚本部分容易修改
    • ~-Golang?是不错,但是咱不熟。-~ 问就是路径依赖
    • 没有 gui,懒得写,也没必要。有一些 cli 工具倒是。
    • 目前使用了 7 个 /24 的 ipv4,及 4 个 /64 的 ipv6,尽管并不是所有的 ip 段都是必须的
    • 其中,IX、TUN 公用一个 /64,因为这些业务都可能直接和外界连接,选择了节约 IP 段
    • LAN 有一个用于 SLAAC 的 /64
    • 另留有一个 /64 用于 LAN 的静态 v6 的 SNPT(历史遗留设计,但是这样比较容易配置 IP)
    • 一个独立的 /64,用于不同于 SLAAC 的 v6 公网地址访问外部时使用(历史遗留设计;实际上可以不使用)

具体实现

  • NET-IX,虚拟交换

    • 软路由的核心,使用一段内网 v4(/24),并分配一段内网 ipv6(/68)
    • 提供的主要功能包括,调度(故障自动处理、分流)及服务(DNS,AC,etc)
    • 其中的主要功能模块:

      • INGW,内网接入网关,同时配置 LAN 侧网关 IP

        • 内置各种策略,以针对不同的接入设备应用不同的实际 DNS 等
        • 可以支持基于 IP(v4,v6)地址配置策略,对于直接接入的设备,也可以通过 MAC 地址配置策略
        • 对于来自 VPN 的流量,采用 XC 网段互联,将流量导入到 INGW 应用策略
        • 对于不同的策略,匹配不同的路由表。DNS 服务的 IP 使用 VIP:对于设置需要过墙的设备,转发到 DNSOW;其他转发到 DNSIW
        • 类似的,还可以配置其他策略,如存在 adblock 策略,可配置 DNS 的 VIP 到带广告过滤的 DNS
      • DNSIW,普通 DNS,dnsmasq 转发运营商 DNS

        • 对于多条宽带,手工配置了两个运营商的 DNS
      • DNSOW,过墙 DNS+ 白名单一次筛选,dnsmasq 设置白名单转发前述 DNSIW,其他转发 RECURSIVE

        • 对于需要 DNS 分流的,全部转给 DNSROUTE/ 或者全部转给 DNSROUTE
        • 使用 EDNS 带内网源 IP
      • OWGW,隧道入口,对于白名单内的 IP 转发至 RTGW。更多详情见 TUN 部分。

        • 使用了国内 IP 列表
      • BLACKHOLE,黑洞,吃掉没用的东西并返回 icmp/icmp6
      • RECURSIVE,递归解析,单栈,bind 实现

        • 为什么没用 mosdns?问就是历史路径依赖,做到这个大概是 2018 年
        • 当然,换成其他递归也没问题
      • DNSROUTE,dns 分流 / 策略分流,c++ 自行开发,后续有详细说明
      • RTGW,IX 网段的出口网关,连接到 WAN。详情见 WAN 部分。
    • 其他次要功能模块

      • AC,AC 控制器,用于控制 AP
      • ADBLOCK,去广告 dns,去广告并转发 DNSOW

        • 使用 EDNS 带内网源 IP
    • 基本分流原理:DNS 白名单 +IP 白名单

      • 对于 DNS 白名单内的域名,转发到 DNSIN,本质上是转发 ISP 的解析的。
      • 对于 IP 白名单内的 IP,当然是直接走运营商出口(交给 RTGW)出去
      • 对于 DNS 白名单外的域名,通过 RECURSIVE 解析
      • RECURSIVE 出来的流量会根据 IP 白名单进行分流
      • 因此,(绝大多数情况下)如果解析的是境内网站,那么最后一次查询权威会访问境内的权威服务器,因此他的权威看到的你的地址是宽带出口,会正常返回境内并且合理的 IP 地址。
      • 如果解析的是境外网站,那么最后一次权威查询的是境外服务器,会看到境外出口 IP 的地址,返回正常的境外 IP。
      • 有的境内网站也有境外权威?是的,但是通常境内权威会快得多。对于屡教不改的个别网站(如果有的话),手工补充进 DNS 白名单就是了。
  • NET-LAN,普通接入

    • 分配独立的内网 v4(/24)和 v6(/64),使用 dnsmasq 和 radvd 管理
    • 考虑到一些设备需要拿到真实 v6,目前采用实际的 v6 分配,所以每次 v6 地址变化需要更新地址以及相应的路由表

      • 也可以直接分配 fd 开头的 IP,反正可以 SNPT
      • SNPT?nft 您的 cli 支持吗?还是继续用 iptables 吧
    • 无 TAG,直接接交换机,墙上的网口都接入这个网段
    • 通过 INGW 与 NET-IX 互联并实现策略
  • NET-WAN,宽带接入

    • 只有链路本地的 v6,使用 fe80::/10 可以转发数据包即可
    • 实现了 PPPoE、DHCP-client 等几个常见的接入,还有独立的 IPTV 接入,通过 udpxy 转发实时节目

      • 因为我拿到运营商送的机顶盒的时候,就已经那样看了好多年了
    • RTGW,连接到 NET-IX

      • 实现了 connmark,用于解决多宽带连入连接的问题
      • 还依赖了 ctdir 来区分连入还是连出连接
      • 对不同的宽带,使用静态路由表 + 子路由表打底默认路由的形式

        • 网速叠加?算了算了。真的用得到极限网速的机会非常少。
        • 而且……这样搞势必跨运营商,效果真的好吗?
        • 真想做的话会考虑在 DNSIW 那里。
    • 对于来自 WAN 侧的数据报文,如果对应的连接没有 connmark,则根据来源 MAC 地址记录 connmark 供后续包使用
    • 对于每条宽带,使用两个 NS,分别是 ISP 和 PROXY:

      • ISP,装有对应的物理网口,设置默认路由为运营商侧
      • ISP 绑定运营商的 v4;对于 v6,这边 pd 到 /60 的段,加黑洞路由,然后将用得到的地址(一个 /62)指向 RTGW
      • ISP 进行 SNAT,对于 v6,进行 SNPT,将内网用到的 v6 地址(包括 LAN 用到的那一段“公网”IPv6 地址)SNPT 成合法的 v6;例如,分配了来自联通宽带的 IPv6 给内网,那么需要从移动出去的时候就会被 SNPT 成移动的 IPv6。
      • 对于 PROXY,包含通往 TUN 网段的虚拟网口,将所有流量都转发给对应的 ISP。用于解决 TUN 中需要指定使用某条线路的问题。这里没有反向路由,反向走普通 RTGW 回去。
      • 如果有一只海外小鸡,这下就可以变成(v4+v6)*(移动 + 联通)一共 4 只了!结合负载均衡或者健康检查一下还能有稳定的出口 IP。
  • NET-TUN,模块化连出 VPN

    • 使用一个独立的内网 v4 网段(/24)、一段内网 v6 网段(/68)
    • 额外接入 OWGW、DNSROUTE 连接到 NET-IX,以及 RTGW 连接到 NET-IX 和 NET-WAN;还有接入 PROXY 连接到 NET-WAN 用于线路选择
    • 对于每一条隧道,均包含两个 netns:TN 和 FW

      • 当前的 /24 可以配置 120+ 个隧道,实际上配置了 100 个
      • TN 用于接受转发过来的数据包,通常是 OWGW 或 DNSROUTE 转发过来的,将这个数据包通过各种协议(tproxy,tun,etc)丢进某个隧道或者丢给 FW
      • TN 的默认路由指向 FW 或隧道,视协议而定
      • TN 和 FW 之间现在有独立的一对 veth 互联,各自设置 noprefixroute,未来有将 TN 和 FW 分开到两个不同网段的考虑
      • FW 默认路由指向 RTGW(自动)或某个 PROXY(手工指定出口)
    • 现在的典型配置是,隧道程序在 FW 运行,个别在隧道上运行的 OpenVPN 等放在 TUN
    • VPN 出口调度:

      • 在 OWGW 中操作
      • 使用类似 RTGW 的 connmark 策略调度各连接
      • 对于有 connmark 的数据包,按照 connmark 调度
      • 除了来自 RECURSIVE 的请求,如果没有 connmark,那就请他去 DNSROUTE 走一圈,除非他刚刚从那边来(看来源 MAC)
      • 如果仍然没有 connmark,匹配路由规则,转发给编号最小的有效隧道

        • 在此 NS 中,存在大量的 ip rule 和多张路由表。好在对应每个隧道的 rule 相对简单,就是指向对应的路由表;对应的路由表也简单,就一条,默认指向某 TUN 的 TN
      • 如果收到来自任何隧道的数据包,记录 connmark 供后续使用(还是看来源 MAC)
    • 自动化健康检查

      • 有额外的脚本去不断检测各隧道
      • 对于质量不佳的隧道删除对应的路由规则但是保留路由表
      • 这样后续新建的链接就不再会进入那些隧道了

        • 但是对于已经打 mark 的连接仍然会保持
        • 避免了切换隧道时大量连接断开,特别是在一个优先级高的隧道变成可用时
    • DNSROUTE 目前使用自行开发的 C++ 程序实现:

      • 匹配 DNS 解析请求报文
      • 如果匹配策略,交给对应的 TUN 解析;对于返回的 A/AAAA 记录,记录源目的 ip

        • 这里有个小问题。如果只有部分域名会经过 DNSROUTE 的话,需要拍平 CNAME。或者就让所有域名都经过 DNSROUTE,只需修改 TTL。都实现了,上线的是前者。
      • 对于转发过来的数据报文,使用类似 LVS-DR 的方法,通过 rawsocket 贴上对应的目的 MAC 地址发给对应的隧道
      • 效率?那当然是比较差了,甚至我是没本事把他塞进 ebpf。不过好在 OWGW 只会把每个连接的第一个 / 前几个报文转发过来,因为一旦有来自隧道的报文回到 OWGW,连接就有了 connmark,后续报文就不再依赖 DNSROUTE 了
      • v6 怎么办?凉拌,做映射表,或者在这部分把 v6 关掉。还记得刚才的黑洞吗?OWGW 上把 v6 的默认路由丢进去就没烦恼了
      • 现在已知一个 bug,没能正确处理碎片包,好在几乎没有影响,后续会修改
  • NET-XC,内部互联,将连入 VPN 导入 INGW 并应用策略(只能应用三层策略)
  • VPN,拨入 VPN,适当实现即可,用 openvpn 或者 wireguard 或者两个都用都没问题
  • NET-IoT,物联网,使用 VLAN,在 AP 上开不同的 SSID。

    • 因为只需要访问境内网络所以从 NET-IX 直接接入默认路由指向 RTGW 即可。
    • 这是历史遗留设计。如果现在设计的话也会走 NET-XC 了,能实现但是懒得改。

为什么说玩得开心呢

  • 当然是在外边也可以进行很多修改

    • 有了新的小鸡 / 要调整某些隧道,只需新增一个 TUN 配置文件,选一个空着的 TUN ID,放到相应目录里,然后 addtunnel ${TUNID}(cli 工具)就好了。删除的话也只需要 removetunnel ${TUNID}。不会影响整体上网,也不影响其他隧道。
    • 调整健康检查也是。健康检查程序分成两部分,一部分负责检查,将觉得可以用的 TUNID 写到一个目录中;另一个用 inotify 或者什么都好听着那个目录,去操作 OWGW 中对应的规则。显然,前一部分也是可以在外边修改甚至停下来的。
    • 多 WAN 支持,甚至可以在外边改一个暂时不用的 WAN。
    • 只有核心的几个模块是必须在本地改的,好在他们现在也不需要什么改动。
  • 还有就是,复杂但是有很大的扩展空间。

    • 在大约 2020 年上线 DNSROUTE,用了源 + 目的双 IP 匹配
    • 后来给每个 TUN 都加了属性,给 DNSROUTE 新增了功能。就是对于来源于某个 IP/CIDR 的,发给带有特定属性的 TUN。

      • 比如我有一只美国小鸡,通过多条 TUN 连接,都含有某个属性
      • 然后可以设置一条规则给 DNSROUTE,让他对来自某个特定 IP 段,比如 192.168.5.0/24 的,转发给这个属性的 TUN
      • 然后我就可以开一个 SSID,比如叫“美国直通车”,通过 vlan 分 192.168.5.0/24 的 IP,手机连上这个 wifi 就可以直通美国了
    • 这么玩还可以隧道串接

      • 比如某个小鸡的出口买了 DNS 解锁
      • 那么就给这个小鸡出口的相关 TUN 都带上某个属性
      • 起一个新的 TUN,叫做“dns 解锁”。其 FW 出口不是通常的指向 RTGW 或者 PROXY,而是指向 OWGW(需要设置 SNAT)
      • 但是同时给 DNSROUTE 设置,来自于这个 IP 的,走某个属性的出口
      • 最后效果就是,即使某条线路不好用了 / 某个宽带断了,只要还能到那个 IP,就能维持解锁
正文完
 0