共计 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 部分。
-
INGW,内网接入网关,同时配置 LAN 侧网关 IP
-
其他次要功能模块
- 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,就能维持解锁
正文完