OpenVPN Connectivity Issue in Public Network

We established a ovpn tunnel between 2 IDCs in September 2014, and we have monitored the availability and performance of two ends for a long time. The geographical distance between 2 IDCs are quite short, but with different telecom carries. mtr shows that there exits ahout 7 hops from one end to the other. The below screenshot shows the standard ping loss.


The result is quite interesting. At first we used UDP protocol, and we often experienced network disconnection issue, later we switched to TCP, and it improved a lot. From the digram, the average package is 1.11%.
Why 1.11%, what I can explain is the tunnel is often fully saturated during the peak hour, and this can't solved at the moment, so no matter what protocol, the package loss should exists. Another possible reason is the complexity of public network which I can't quantitate.
The current plan works during current background, but no "how many 9s" guarantee. Anyway, if we want to achieve more stable connectivity, a DLL(dedicated leased line) is a better choice.

耗时两天排查问题的小结

最近在线上遇到了一个比较棘手的 ops 问题,我花了两个工作日加上中间的一个晚上时段才最终发现了问题的根源,下面分享下我排查问题的思路以及步骤,先介绍下背景。
三个 IDC,BTC, DG, DL,其中 BTC, DG 是通过光纤直连,内网互通,DL 通过与 BTC 之间建立 OpenVPN tunnel 实现了三个 IDC 内网的打通。这三个 IDC 部分的 VLAN 网段信息分别如下:
BTC: 172.18.10.0/24,BTC 这台机器的 OpenVPN client IP 为 172.18.10.254
DG: 172.20.0.0/24, 172.20.100.0/24, 172.20.200.0/24 等等
DL: 192.168.1.0/24,192.168.1.24 这台 OpenVPN 作为 server 跟 BTC 的 172.18.10.254 建立起 tunnel
由于涉及到公司的商业信息,以上是我简化后的模型,实际的情况、涉及到的因素要比这个复杂的多。

某日下午我做完一个常规性的变更之后,收到反馈说从 DG 172.20.100.0/24 的机器无法访问位于 DL 192.168.1.26/24 的这台机器。
很自然的,我从 172.20.100.0/24 上挑选了 172.20.100.1 这台机器,登录上去,确认了下面的几件事:
1. 基本的网络连同性,ping 192.168.1.26,发现无法 ping 通。

2. 看 route 信息,通过 mtr 看路由信息:
$ sudo mtr 192.168.1.26 -n -r
HOST: host-1.umops.us             Loss%   Snt   Last   Avg  Best  Wrst StDev
  1. 172.20.100.249                 0.0%    10    0.5   2.0   0.5  10.4   3.3
  2. 172.16.1.254                  0.0%    10    3.8   2.1   0.7   7.4   2.3
  3. 172.18.10.254                  0.0%    10    0.3   0.4   0.3   0.4   0.0
  4. ???
可以很清楚的看到,到了 BTC 的 OpenVPN client 之后就断了,也就是没有路由指向了。

2. 基本的 networking,从 ifconfig 以及 /etc/sysconfig/network-scripts/ 来看,没有问题

3. 基本的 route:
jaseywang@host-1:~$ route  -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
172.20.100.0     0.0.0.0         255.255.255.0   U     0      0        0 bond0
0.0.0.0         172.20.100.254   0.0.0.0         UG    0      0        0 bond0
没有问题,默认 GW 直接上我们 Nexus 7000 的 HSRP VIP(172.20.100.254),上面看到的 172.20.100.249 是 RIP。

接着我把 172.20.100.0/24 的其他几台机器挨个试了一遍,发现现象跟上面一样。

接着,我登录到 DG 的其他网段比如 172.20.0.0/24 的机器上做同样的测试,可以发现完全没有问题:
$ sudo mtr 192.168.1.26 -n -r
HOST: host-2.umops.us               Loss%   Snt   Last   Avg  Best  Wrst StDev
  1. 172.20.0.248                  0.0%    10    0.7   0.5   0.4   0.7   0.1
  2. 172.16.1.254                  0.0%    10    5.7   2.3   0.6   8.6   2.8
  3. 172.18.10.254                  0.0%    10    0.3   0.3   0.3   0.4   0.0
  4. 192.168.4.1                   0.0%    10    6.9   5.6   2.6  24.5   6.8
  5. 192.168.1.26                  0.0%    10   17.6  10.2   2.9  36.2  10.5

当时第一想法是,是不是 DG 的 SW 配置有问题,是不是把 172.20.100.0/24 这个网段在某些关键的点上漏掉了,于是让我们的 ne 上去确认,反馈说没有问题。事后总结起来,其实从 mtr 上就能看到应该不是 SW 的问题,mtr 可以看到到了 hop 4 才中断,否则第一或者第二 hop 就断了。

并且诡异的是,DG 的其他网段的机器都没有问题,唯独 172.20.100.0/24 的有问题,我依然认为是整个从端到端的某个设备或者服务对 172.20.100.0/24 这个网段少做或者多做了一些变更,但是从端到端,期间涉及众多的机器,众多的不同同层的因素,从服务器到交换机再加上 VPN,确实需要考虑进去的因素比较多,暂时还没有很好的缩小问题的方式。

第一天晚上回去的时候想起来可以反向试试,于是从 192.168.1.26 这台机器 mtr 到 172.20.100.1,hop 如下:
$ mtr 172.20.100.1 -r -n
HOST: github-jaseywang-me            Loss%   Snt   Last   Avg  Best  Wrst StDev
  1.|– 192.168.1.24               0.0%    10    0.5   0.4   0.3   0.5   0.1
  2.|– ???                       100.0    10    0.0   0.0   0.0   0.0   0.0
可以发现,IDGP 包到了 DL 的 OpenVPN server 就中止了。

至此,我基本可以将问题的范围缩小到连接 BTC 跟 DL 的两台对端的 OpenVPN 机器上,并且从 hops 信息来看,应该是 DL 的 OpenVPN server 有问题的概率更大。

晚上回去之后,我一边在 192.168.1.26 这台机器上 mtr 172.20.100.1,一边通过 tcpdump 抓包,可以发现只有 syn,没有任何的 ack。同样的,mtr 172.20.0.1 这类的机器却没有任何的问题,有 request/reply。这更加确定了我之前的想法,一定是这个端到端的跨越了 3 个 IDC 的某个子系统出了点问题,并且这个问题可以归结于,把 172.20.100.0/24 这个段给特殊对待了。

至于是哪台机器的哪个服务出现了问题,这个就是我那天晚上以及第二天上午做的工作。当天晚上,我又确认了下面的几件事:
1. 一一排查了这两台 OpenVPN 机器的 iptables,为了以防万一,我清空了 INPUT 做测试,现象依旧
2. 再三确定 SNAT 对 OpenVPN 影响很大的这个 chain 配置没有问题
3. selinux 关闭了,sysctl 里面也没有特殊的开关
4. 基础的系统配置,包括我最开始提到的 networking、route 等信息
5. 172.20.100.0/24 里面的机器跟其他比如 172.20.0.0/24 机器的差异,由于都是统一定制安装的,在基础的系统层面根本找不出差异
6. OpenVPN 的配置,把 client, server 的两个文件检查了好几遍,没有发现异常

第二天上午继续排查了两个多小时,能想到的可能会涉及到的方面都想到了,依然没有结论。转而向我们的总监求助,跟他描述了下问题的情况,跟我一样,一样觉得很诡异,同样的配置,其他 VLAN 的机器完全没问题,就这一个 VLAN 的有问题。要是整个 DG 的 VLAN 都不通,那还能说明些问题,现在仅仅这一个 VLAN 不通。
下午,我们又从下面几个方面入手逐一排查:
1. 依然怀疑是 route 层面有问题,并且是 DG 那台 OpenVPN server 本机的 route 有问题,于是直接上了 iproute2 来确定 route table 是否正确,我也临时熟悉了 iproute2 的一些基本概念。通过下面几条 cli 判断了几个不同类型的 routing table 的情况:
# ip rule list
# ip route get 172.20.100.1

2. tracapath 继续跟踪了番,现象依然是到了 DG 的 OpenVPN server 就断了

3. 通过 starce 查看系统的调用显示跟上面一样的现象

4. 由于这两台 OpenVPN 的机器都是跑在 Xen 上的 instance,怀疑可能是由于 VM 某些方面的问题导致的,但是考虑到使用的是 bridge 并且要排除 VM 导致可能性的的繁杂程度,暂时放弃深挖。

5. bonding 方面的影响,由于我们对 DG 的所有机器刚做完 10G 的升级,包括 172.20.100.1 这台机器都使用了 bonding,尽管没没有理由怀疑是 bonding 造成的,我们还是把这台机器的 bondin 给拆了,换成了普通的单网卡模式进行测试,结果很明显

6. 抓包,在两端的 OpenVPN 上抓包以及在上联的交换机做 mirror,得到的现象跟上面一样

7. 扩大 OpenVPN server 的 netmask,既然 172.20.100.0/24 这个跟其他的诸如 172.20.0.0/24 等地位一样,将原先的 /24 扩大到 /16 以包含所有的 IP,就没有理由不通了,这个是修改前的 route:
# route  -n

172.20.100.0     0.0.0.0         255.255.255.0   U     0      0        0 tun0
172.20.0.0       0.0.0.0         255.255.255.0   U     0      0        0 tun0

修改后的 route:
# route  -n

172.20..0       0.0.0.0         255.255.0.0   U     0      0        0 tun0

意外的是,依然不通

8. 在 DG 新启用了一个 172.20.150.0/24 gateway 为 172.20.150.254 的 VIP,在 DL 的 OpenVPN server 上添加到到这个 IP 的路由:
# route  add -net 172.20.150.0  netmask 255.255.255.0 tun0

在 192.168.1.26 的机器上测试,现象依旧:
$ mtr 172.20.150.254 -r -n
HOST: github-jaseywang-me            Loss%   Snt   Last   Avg  Best  Wrst StDev
  1.|– 192.168.1.24               0.0%    10    0.4   0.5   0.4   1.1   0.2
  2.|– ???                       100.0    10    0.0   0.0   0.0   0.0   0.0

把以上所有的现象以及测试结果综合起来看,几乎可以可以确定就是 OpenVPN server 的问题,但是哪里有问题,依然不清楚。 这时我们突然想到还有这台 OpenVPN server 的 log 没仔细检查一边,google 了下,通过 "verb 5" 提高了 debug 级别,这下就能很详细的看到 OpenVPN 打的 log。log 的格式比较奇怪,像下面这样的,但是这不重要,下面这么多的字符,好好看,能发现 "packet dropped" 的关键词:
wrWrWRwRwrWRwrWRwRwrWrWRwRFri Apr 18 17:05:02 2014 us=291047 btc/111.111.111.111:45721 MULTI: bad source address from client [111.111.111.111], packet dropped
RwRwRwrWRwRwRwRwRwRwrWRwrWRwrWrWRwrWRwRwrWRwrWrWRwRwrWrWrWRwrWRwRw
RwRwRwRFri Apr 18 17:05:10 2014 us=132323 btc/111.111.111.111:45721 MULTI: Learn: 10.8.0.38 -> btc/111.111.111.111:45721
RwrWRwRwRwrWrWrWRwRwrWrWRwrWRwRwRwrWrWrWRwRwrWrWRwrWRwRwRwrWrWR
i Apr 18 17:06:07 2014 us=891600 btc/111.111.111.111:45721 MULTI: Learn: 10.8.0.126 -> btc/111.111.111.111:45721
RwrWrWRwrWrWRwrWRwRFri Apr 18 17:06:29 2014 us=146563 btc/111.111.111.111:45721 MULTI: Learn: 10.8.0.26 -> btc/11
1.111.111.111:45721

Google 第一条就告诉了我们答案!原来 172.20.100.0/24 作为我们较新的网段,我忘记把 iroute 写到 ccd 文件里面了:
These errors occur because OpenVPN doesn't have an internal route for
192.168.100.249. Consequently, it doesn't know how to route the packet to this
machine, so it drops the packet.
Use client-config-dir and create a ccd file for your client containing the
iroute option to tell OpenVPN that the 192.168.100.0/24 network is available
behind this client.

"MULTI: bad source address from client [192.168.100.249], packet dropped" or
"GET INST BY VIRT: 192.168.100.249 [failed]"?

下面提几个跟 route 关系较大的指令(1, 2) ,以后 end-to-end, sub-to-sub 出现问题,不妨先考虑是不是这些方面造成的。
1. client end 的 route
;push "route 192.168.10.0 255.255.255.0"

2. client subnet 的 route
;client-config-dir ccd
;route 192.168.40.128 255.255.255.248
;route 10.9.0.0 255.255.255.252

最后总结一下,这是一次刺激的发现问题找到根源解决问题的过程,又一次验证了整个 *nix stack 里面最复杂的是 networking 这个问题,因为牵扯到层面太多。正所谓「山穷水尽疑无路,柳暗花明又一村」。不吹牛逼的说,做了这么多年,还真没有我解决不了的工程问题。

openvpn 证书吊销(revoke)的问题

执行的命令很简单:
# ./revoke-full test_1

revoke 之后,index.txt 对应的条目会由 V 变成 R。另外,会在 keys/ 下面生成 crl.pem 的 CRL。
在使用过程中发现,需要将其 ln 到 /etc/openvpn 下面:
# mv /etc/openvpn/keys/crl.pem /etc/openvpn/.
# cd /etc/openvpn/keys
# ln -s /etc/openvpn/crl.pem .

还需要在配置文件里面指明其位置:
crl-verify crl.pem

否则会出现权限错误等问题:
CRL: cannot read: /etc/openvpn/keys/crl.pem: Permission denied (errno=13)
Exiting

重启即可。


在用 build-key 生成证书的过程如,如果后面的 CN(common name) 跟之前的一样,那么或出现下面的问题:
Certificate is to be certified until Jun 10 06:58:00 2023 GMT (3650 days)
Sign the certificate? [y/n]:y
failed to update database
TXT_DB error number 2

自然不推荐使用同样的 CN,如果一定要使用,可以将 index.txt.attr 文件里面的 unique_subject = yes 改成 unique_subject = no。这样在 index.txt 里面看到的就是两个了:
# tail index.txt -n 2
V 230810065719Z   46  unknown /C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/CN=test_1/emailAddress=me@myhost.mydomain
V 230810070508Z   47  unknown /C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/CN=test_1/emailAddress=me@myhost.mydomain

这个时候如果需要 revoke,需要单独的对每一个 test_1 进行 revoke,注意不要把两个 CN 的证书搞混淆了。

最后,如果蛋疼的想 unrevoke 证书,可以参考这里

跨机房部署

跨机房的服务部署分两个小问题。第一个是系统(RAID、BIOS、OS) 的部署,另外一个则是 OS 上面的服务的部署。

系统
曾经想通过 dhcp over openvpn 来解决机房的"互联互通"问题,目前暂时仅仅解决了通过 openvpn 进行 lan to lan 的连接,而 over openvpn 的 dhcp(dhcrelay) 到目前为止还不清楚为什么获取不到 IP。

后来看 cobbler 的文档的时候发现可以通过 replicating 实现,这个需要在对端的 IDC 部署一台 cobbler server,由这台机器完成 tftp, dhcp 的功能,而由 central cobbler 实现统一的 system, repo 等配置。
部署起来也很简单。在对端部署一台跟 central cobbler 一样的 cobbler,默认不需要添加任何的 distro 等。该端所有的配置均从 central 拉取,因此需要足够的硬盘空间,尤其要同步 repo:
# cobbler replicate cobbler replicate –master=cobbler.example.org [–distros=pattern] [–profiles=pattern] [–systems=pattern] [–repos-pattern] [–images=pattern] [–prune] [–omit-data]

接下来该建立 RAID 的建立 RAID,该安装系统就安装系统。另外,官方推荐通过 trigger 机制来自动的同步 central 上的数据,这个我后来调研的时候发现并不是很好,如果有多个机房的话,会造成 system 的混乱。因此,暂时还是手动的在对端的 cobbler 上手动的执行一遍 replicating,每次机器上架的时候的才会用到,工作量并不大。

服务
这个相对就比较简单了,如果是通过 tunnel 形成的一个三层结构,那么通过 puppet master 或者类似的 saltstack 就能很轻松的完成,前提是注意 ntp 以及 dns(hosts) 的统一正确。

跨机房的网络通信

这个实现方式有很多种,有条件的可以走光纤专线;没钱的,开 VPN,其中关于 VPN 的实现方式有很多种,这里使用 OpenVPN。由于没有二层的需求,就不考虑使用 TAP(server-bridge) ,这里使用 TUN(server) 设备。
这里,我们把 client/gw 分别部署在 A、B 两 IDC GW 上,即 SNAT 的出口上。

各种复杂情况的组合无外乎下面两种情况的组合叠加:
1. Including multiple machines on the server side when using a routed VPN (dev tun)
这个模式就是我们最普遍使用的模式,科学上网以及一般内网的登录都是使用的该模式

2. Including multiple machines on the client side when using a routed VPN (dev tun) **
这个是让 server 访问 client 后面的机器,需要 client-config-dir 指令来维护路由

这里有个文档,涉及就是上面两种情况的结合,不过是通过普通的家用路由实现的,思路非常的好。而下面,我们会通过更 "纯正" 的方式实现上面的情景。

我们这里有 A、B 两个机房:
A(server): eth0(public), eth1(192.168.1.0/24)
B(client): eth0(public), eth1(10.18.10.0/24)

双方的 vpn 地址在 192.168.4.0/24 里面。

对于 A 的 server.conf 来说,最重要的就是下面几句话:
$ cat /etc/openvpn/server.conf
push "route 192.168.1.0 255.255.255.0"
client-config-dir       /etc/openvpn/ccd

$ cat /etc/openvpn/ccd/B
iroute 10.18.10.0 255.255.255.0

接着就是在 GW 上做 SNAT:
-A INPUT -s 192.168.4.0/24 -j ACCEPT
-A POSTROUTING -s 192.168.4.0/24 -o eth1 -j MASQUERADE

增加到 10.18.10.0/24 的 路由:
# route add -net 10.18.10.0/24 netmask 255.255.255.0 gw tun0

对于 B 来说,client.conf 跟正常的一样,没什么需要特别注意的,接着就是在 netfilter 上添加 SNAT:
-A INPUT -s 192.168.4.0/24 -j ACCEPT
-A POSTROUTING -s 192.168.4.0/24 -o eth1 -j MASQUERADE

最后要注意的是,双方同时开启 ip_forward。理清楚了,就这么简单。

通过 OpenVPN 隧道广播 DHCP

lan-to-lan(AB 两个 IDC) 的 OpenVPN(tun) 网络,A 端的 lan 里面部署有一台 DHCP,想通过隧道广播到 B 端内网里面,这个明显是不可能的,中间需要跨越多个网络。因此可以通过 relay 实现。
在 B 端的 GWb 上部署一台 dhcrelay,将其指向 A lan 的 DHCP,按理来说 B lan 里面的机器这下可以获取到从 A 过来的 relay 了,可是试了几次都没有成功。调试过程中用到了如下的两个工具。

一个是 dhcpdump。还有一个是 dhcping,这个用的比较多,可以模拟做 request:
# dhcping -h 00:12:3f:20:11:49 -s 10.1.200.200 -c 10.1.200.1 -V

或者
# dhcping -s 255.255.255.255 -r -V
在 g 上搜 "dhcp over vpn" 一搜一大把,应该是有不少成功的案例的,我这里为什么获取不到 IP,到目前为止还不清楚,就当抛砖引玉了。