由于小昊黑网络的逐渐发展壮大,从最初的接入老王家和长城宽带大法好,到现在的 上海-东京 APG(感谢某富商的赞助),深圳/广州-香港传输(感谢鸽子王厦主和郑总的赞助) 和 北京-法兰克福陆缆(感谢某来总都赞助)的上线,最早的 RouterOS 手糊大法已经满足不了需求了。

网络内有什么

要解决问题,先要考虑一下网络内有什么。当前的节点大概可以分为3种类型:

  • 数据中心节点
    • 国内数据中心节点
    • 海外数据中心节点
    • 具有跨境传输的数据中心节点
  • 家庭节点
    • 国内家庭节点
    • 海外家庭节点
  • 办公室节点
    • 国内办公室节点

由于网络内的类型不同,工作方式也不一样,国内节点主要为用户接入,而海外的数据中心一般都作为NAT出口。所以需要解决的问题有2个方面:

  • * 所有节点的互联互通
  • * 国内加速访问全球互联网

组张大内网

设计目标:

  • 支持随意增加/减少节点,互联拒绝单路由,单点故障对整体网络没有影响
  • 大内网内没有 NAT,全部支持三层可达
  • 边缘入口具有安全能力,可以拒绝/放通
  • 提供 DNS 给内网用户,保障国际互联网访问

用 BGP 来管理网络

为了可以方便随意增加/减少节点,让增加的节点可以自动发布路由,让掉线的节点自动把路由在网络中取消并改变转发路径
个人认为选择 BGP 来组网是一个很好的方案,BGP 灵活的配置还可以随意调整路由的优先级并且给一些特定需求制作特定的策略

我们的目标是:没有NAT

要解决第一个问题「没有NAT」,首先打开 Excel 为所有节点设计好它独立的:

  • 地址段 - 10.0.0.0/8172.16.0.0/12 分别分配给数据中心和家庭节点
  • 路由器 loopback 地址 192.168.250.0/24
  • AS号

地址段: 用 RFC1918 所规定的地址来分配给网内节点,根据节点的规模大小分配不同的地址段。
路由器 loopback 地址: 单独选用一段 RFC1918 地址作为 loopback 地址,每个路由器分配 /32
AS号: 用 RFC 6996 所规定的 Reserved for private use 4200000000–4294967294 来给内网分配 ASN,我选择了 4200000xxx,最后3位与路由器 loopback 地址对应。

边缘入口の安全能力

为了解决这个问题,祭出近两年折腾比较多的 FortiGate,FortiGate 在防火墙功能上没什么好说,而且简单明了的 web 配置让隔壁老王也可以愉快配置自己的安全策略(这样我就不用操心了)

为了解决组网和出口保护,用飞塔的 BGP 和 SD-WAN 来维护大内网互联与互联网出口的可靠性。
对于 BGP 来组内网,前面已经描述了为什么选择 BGP,在后面组大网再详细描述怎么样使用 BGP。

对于互联网出口,因为近些年移动的疯狂宽带赠送活动,让大家的家里都有了2条甚至3条互联网出口带宽,为了更好地利用资源,把互联网出口全部挂在 FortiGate 上并使用 SD-WAN 进行选路和线路保护让人觉得非常省心,有一次家里的主线因为网继续费停机,由于飞塔 SD-WAN 的存在把互联网流量全部转到了移动宽带,根本没有感受到主线的中断。

飞塔官方对 SD-WAN 功能有详尽的介绍,甚至支持用飞塔的 SD-WAN 结合 IPsec、专线等方式直接组网,详见:https://www.fortinet.com/cn/products/sd-wan

路由器

为了可以跑 BGP,那就有必要选择一款顺手、能达到需求支持 BGP 协议的路由器,市面上常见的支持 BGP 的路由器(防火墙)有:

  • Mikrotik RouterOS
  • VyOS
  • 6wind
  • Juniper MX/SRX/QFX/EX/cRPD
  • Cisco ISR/ASR/Nexus
  • Huawei
  • FortiGate
  • PaloAlto

RouterOS 可以说是爱折腾小伙伴最喜欢的路由器(可能没有之一)了,RouterOS 也可以支持 BGP 协议,小昊第一代黑网络就是使用 RouterOS 来构建的,但是 RouterOS 7 的发布,并没有解决 RouterOS 6 BGP 功能中的一个大问题:提供给人用过滤器和 community 设置界面,Router OS 7 暴力的直接一个空白文本框让人根本不想拿它继续跑 BGP 😅

VyOS 是一个基于 Debian 魔改而来的路由器系统,其实就是一个 Linux 内核的操作系统,BGP 支持很ok,但是小 bug 比较多,比如某些版本更新 prefix 过滤器总需要重新建立 BGP session 才能刷新,这实在是让人有点难受。

6wind 功能无敌,性能在软路由里几乎无敌,但是 开起来 CPU 起飞,不适合家庭和绿色 IDC 场景。

Juniper Junos 是我用过的觉得最好用的能跑 BGP 的平台了,从300元的 EX2200 到几十万的 MX 系列,甚至配置都可以一毛一样,而且功能完善。在 cRPD(Containerized RPD) 的出现以后,简直是云网融合大法的首选了。(Juniper: 什么是 cRPD - https://www.juniper.net/documentation/us/en/software/crpd/crpd-deployment/topics/concept/understanding-crpd.html)

Cisco 除 Junos 以外另一大运营商级 BGP 路由器平台,可惜价格和功耗不适合。

Huawei 华为的交换机、路由器也都对 BGP 有良好的支持,但是和 Junos 比总觉得体验还差了一点。(正在拿华为 BGP 起 VXLAN 做专线,体验一级棒)

FortiGate 支持 BGP,但是支持得不多,在本案里仅作为 IGP 路由给组网路由器发布内网路由。

PaloAlto 我何德何能能用上高贵的 PaloAlto?

Bird Linux 发行版 + Bird 是在 cRPD 之前我选择的解决方案,无奈 junos 的一致性实在太好,用起来太好用,让我放弃了🐦。

启动组网

每个组网节点的标准配置会有一台搭载 vmware broadcom ESXi 或者 Proxmox VE 的虚拟化小主机,并安装 Ubuntu + cPRD 作为组网路由器。
简单拓扑

其中 蓝色 互联线为点到点的裸纤,橙色 互联为经过互联网的 TAP 通道。
为每个路由器设置它的 router-idAS Number,用一对一对又一对的互联地址把 BGP 路由建立起来。

为什么不用 TUN? 用 TUN 的话,过三层的路由处理比较麻烦,所以使用 TAP + /30 互联来做路由器间的互联

简单定义:loopback 地址/ASN 最后为个位数的,是数据中心节点,最后为 1xx 的为家庭节点
这时候,对于数据中心节点 192.168.255.1, 192.168.255.2 只提供数据转发,所以只 export 它路由器的 loopback 地址,对于家庭节点,虚拟路由器再与家里的 FortiGate 防火墙互联,由 FortiGate 防火墙发出内网地址并由组网路由器打上「需要组网互通」的 community。

路由发布

发布家里的内网地址

内网地址由 FortiGate 给组网 cRPD 路由发布 IGP 路由,在 cRPD 上打上「需要组网」的 community 65535:1 给到全网,对于数据中心,它只转发带着 65535:1 这个 community tag 的路由,这样 1)避免了家里部分不希望被外网所知的地址段进入,2)避免家里因为历史原因,有与网内冲突的地址段发布到内网。

对于转发的数据中心节点,大致的转发策略如下:

policy-options {
    policy-statement bgp-neighbor-import {              // 对于从内网邻居收到的路由,先收了再说
        term default {
            then accept;
        }
    }

    policy-statement bgp-neighbor-export {              // 过滤要发布给内网邻居的路由
        term local {
            from {
                route-filter 192.168.255.1/32 exact;    // 发布自己的 loopback 路由
            }
            then {
                community add COMM-LOCAL;               // 标记 community,COMM-LOCAL = 65535:1
                community add COMM-HK-EXPORT;           // 标记 community,从香港发出
                accept;
            }
        }
        term neighbor {
            from community COMM-LOCAL;                  // 对于携带「内网 & 需要组网」 community 的路由
            then {
                community add COMM-HK2-EXPORT;          // 再加上一个「从香港发出」的标记,方便后续调整
                accept;
            }
        }
        term default {
            then reject;                                // 不满足以上条件的不转发
        }
    }
}

在另外一头,由于鸡鸡哥的出口位于东京,在组网的同时也为内网提供互联网优化路由,即鸡鸡哥家发布带 community 标记的国际路由,网内用户选择要不要接受此路由并应用到路由表。同理也适用于厦主节点,因为出口为 HKT,也提供了对应的国际路由发布。终端用户在使用时,只用选择对应国际互联网出口发来的路由,并过滤 community,即可选择出口方向。由于 NAT 行为都在真正接入「互联网」侧发生,所以必须要让NAT的边缘路由器收到客户端的 IP 广播,即可完成出、入包处理。

DNS

在完成组网操作后,解决访问国际互联网的「DNS地址污染」问题也是一个重点问题,由于当前在国内已经有了 15+ 节点,互联网出口分布在电信、联通、移动等多个运营商,所以制作适合该运营商+正确访问国际互联网的 DNS 也是一件非常重要的事。

但是每次上线一个节点就要部署一套 DNS 是不现实的,由于部署复杂度和相关虚拟机资源的原因,并不能保证让所有边缘节点都支持本地 DNS 解析。因为我们的网络是互联互通的,通过 BGP + Anycast + 运营商标记即可让没有本地 DNS 的用户选择一个 就近同运营商 DNS 来实现国际互联网访问。

DNS

在上图所述的网内,有3个 业务地址100.66.66.66100.66.88.88 的 DNS,但是在收到的路由中,他们的路由携带了不同的 community 65535:401(定义为电信), 65535:402(定义为联通), 65535:403 (定义为移动)用来区分不同运营商。

我们使用 show route 100.66.66.66 查看路由器收到的路由,可以看到类似以下的结果:

> show route 100.66.66.66

inet.0: 1521 destinations, 1891 routes (101 active, 0 holddown, 1421 hidden)
+ = Active Route, - = Last Active, * = Both

100.66.66.66/32    *[BGP/170] 3w2d 02:48:45, localpref 0
                      AS path: 4200000007 I, validation-state: unverified
                    >  to 100.64.0.149 via veth3
                    [BGP/200] 1w2d 18:15:46, localpref 0
                      AS path: 4200000004 I, validation-state: unverified
                    >  to 100.64.0.157 via veth4
                    [BGP/200] 1w3d 22:34:58, localpref 0
                      AS path:  4200000009 I, validation-state: unverified
                    >  to 100.64.0.86 via veth2

从结果中可以看到3条匹配的路由,但是 preference 的值不同,被选中的路由是下一跳 100.64.0.149 的路由,再执行 show route 100.66.66.66 detail 可以看到,被选中的路由 community 为:

...
Task: BGP_4200000007.100.64.0.149
Announcement bits (3): 1-KRT MFS 2-KRT 3-BGP_RT_Background
AS path: 4200000007 I
Communities: 65535:1 65535:401
Accepted
Localpref: 0
...

因为我在选择路由时,根据当前的运营商,优先选择了 65535:401(定义为电信)作为当前激活的路由,即可使用电信+国际解析的 DNS 了。

为内网用户提供公网映射

内网内有一用户名为大师,其中一个节点位于他的办公室,由于办公室的 IT 所限,一定没有公网,但是他又有到处外部接入办公室某工作站的需求。
基于最近阿里云热的 200Mbps 峰值带宽轻量服务,又双叒部署了一台 路由 节点并加入了内网,因为阿里云节点具有的静态公网 IP 地址,那必须用端口转发把这静态地址作为远程接入的 IP。

感谢伟大的 iptables,在启用内核转发 net.ipv4.ip_forward = 1 后,使用 iptables 把互联网入的请求转发给内网的工作站:

iptables -t nat -A PREROUTING -d <云路由器IP> -p tcp --dport <对外服务端口> -j DNAT --to-destination <内网主机:端口>
iptables -t nat -A POSTROUTING -d <内网主机> -j SNAT --to <本机 loopback 地址>

通过云端路由器把请求(注意 tcp 还是 udp)转发给内网地址,同时把请求源 SNAT 成 loopback 地址以保证回程路由还是能回到本机。
即可把内网特定端口暴露到公网


– 未完待续 –