如何解决动态IP导致的VPN失效
2019-01-17
文章主要分三个部分,前面的偏基础,重点在最后第三条,是写的如何解决动态ip导致的一系列问题。有需要的话,可以直接到最后查看。
想要实现的目标:
1.通过公司内网的网段可以直接访问阿里云上的服务器主机,VPN 可以将一个机构的多个数据中心通过隧道的方式连接起来,让机构感觉像是在一个数据中心里面。
2.在公司之外,无论在祖国各地,五湖四海,只要是能联网的地方,就能访问到公司内网。
具体的实现
1.打通公司内网到阿里云的VPN通道
首先要说的就是通过公司内网访问阿里云上的服务器。
这样说比较抽象,举个例子,如果我阿里云上的某台服务器的ip是188.100.100.1
,那我要实现的效果是:
1 | ping 188.100.100.1 |
能够ping通,当然首先要保证的是我这台服务器的ip和公司内网的网段不冲突,也就是说阿里云的vpc(Virtual Private Cloud可以理解为分配给服务器的私有网段)和公司内网正常办公的网段不冲突。
在这里要注意的一点,是先设置路由器,然后在去阿里云上做设置。
在阿里云中做这些设置:
vpn网关:
这里的vpn网关,一般指的是公司服务器集群总的网关,是一个固定IP作为网络的输入口,这里我们是不用新建设置的。注意这里的ip,是我们要用到的。
用户网关:
用户网关这里的ip就是我们公司的外网ip,获得外网ip的方法有很多种,比如访问http://www.ip138.com/
,这里需要我们新建创建用户网关
来填入我们的ip。
IPsec连接:
这一步就是集大成的关键性一步了。
这里的VPN网关和用户网关都选择我们上一步设置好的选择就好。这里的本端网段
填写的是阿里云的VPC网段。而对端网段
填写的是我们公司内网分配给设备上网的网段。
还有就是约定好喝公司的网关路由器上的共享秘钥。和加密算法之类的,保证云上和本地配置的一致。
重要的一点
:如果你保证你配置的没问题,但还是提示‘第二阶段协商未成功’这样的错误。很可能是因为比较玄学,要先去路由器做好配置,然后在阿里云上做好配置,要保证一个顺序的先后性。
2.打通外部到公司内部的VPN通道
也就是下图中的移动办公的这种场景:
要达到的效果说的实例化一点就是
1 | ping 188.100.100.1 |
能够ping通,值得注意的一点是这里的188.100.100.1
是阿里云上服务器的内网ip,也就是说,并不只是从公司外访问公司内的某台设备那样简单,而是要穿过两层vpn隧道。
这里要在网关上分配一个网段,来给进来的设备。相当于从外部访问的设备进来后,就相当于一个内网的设备。
采用的是PPTP协议连接。在路由器上做些配置就OK了。
Windows电脑自带PPTP,Mac电脑可以用Shimo软件连接。填写公司的ip地址,和分配给你的username和password就好了。
这样后,可以ping通内网设备,但是并不能ping的通188.100.100.1
。这个问题很奇葩。查了很多资料,终于找到了这个办法试了试。
但这里值得注意的一点是:在一些路由器上,如unifi路由器,在输入框中是不允许填写和内网网段重复的网段的。所以,我的做法是到路由器中,改写etc/pptpd.conf
这个文件,设置remoteip,把它的网段改为和内网相同的网段。这里还有一点是值得注意的是,分配的ip不要和已有的分配给内网的设备的ip重复。所以我的做法是选择了ip网段的后面几位。localip是pptp的服务器的地址。如果用traceroute命令查看,访问非局域网,会有显示这个地址。
更改后,保存,并使其生效。重启生效的命令为:
1 | sudo service pptpd restart |
3.精华:解决动态IP导致的问题
如今,无论是对于家庭网络还是公司网络(家庭网络与公司网络一个重要的区别是:公司网络是上传和下载的速度相同),网络运营商默认提供的网络,都不是固定IP,而是动态IP。如果我们去找网络运营商去要求固定IP,运营商会很客服的告诉你,当然可以提供这项套餐的,然后转而告诉你,这个得加钱。而固定套餐的价格相比于动态IP的套餐的价格要高出数倍。
因为遇到了动态IP,使得我原有的设置,复杂了至少两倍。我所遇到的困难:
IP地址经常更换,每次重启路由器后,都会导致IP变化。这使得如果IP每次变化后,我都需要到阿里云上重新新建用户网关和IPsec连接,同时配置本地网络。还有pptpd.conf文件也会重新恢复原样。
Unifi网关的路由器系统的Linux版本,对linux包源进行了限制,比如wget都不能下载安装,所幸提供了Python,但是也仅仅支持Python2,Python的第三方包也不能下载,比如常用的requests包。
我们把这些问题拆分化,一一解决:
通过阿里云API接口来创建IPsec连接
对于创建用户网关和IPsec连接,阿里云是提供一个公共的API接口用户创建和删除的,可以参考这个链接阿里云VPN接口文档
当然并不只是看文档这么简单,文档里并没有提供Python的示例代码。搜索后发现一些都是Java代码,零星的几个Python代码还跑不起来。所有这里直接黏贴上我组合后的Python2代码,Python3只要稍稍改动部分,就可以跑的起来,这个我稍后会写。
Python2调用阿里云API示例代码:
1 | # -*- coding: utf-8 -*- |
关于这块代码的原理可以参见这篇文章阿里云 API 签名机制的 Python 实现
将此段Python2的代码变为python3的代码,需要改动两个地方,一个是
import urllib
变为import urllib.parse
,还有就是就是转码python2为bytes(bs).encode('utf8')
,python3为bytes(bs,encoding='utf8')
阿里云提供的文档中,有一个容易引起人们误解的地方就是:
我在代码中要构建一个字典,需要构建key和value,这里的key值,不要填写阿里云文档中的IkeConfig.Psk
这样的格式,而是添加PSK
,如下:1
2D['IkeConfig']={"Psk":"",'IkeVersion':'','IkeMode':'','IkeEncAlg':'',
'IkeAuthAlg':'','IkePfs':'','IkeLifetime':'','LocalIdIPsec':'','RemoteId':ip}其他示例的Python代码中还有一种情况没有涉及,就是因为我需要在LocalSubnet那填写网段,这个网段里包含“/”字符,所以需要处理“/”这个字符的转换,所以我添加了如下的代码。
1
res = res.replace('/', '%2F') #That is for the LocalSubnet "/"
如何监测IP变化
方案是这样:在路由器的linux系统中通过crontab跑一个定时检测ip变化的脚本,如果有变化则触发相应的后续的一系列创建和修改的操作。所以包含四部分:
1.crontab怎么写?
2.如何获取实时公网ip?
3.在哪找到源ip来实时的与当前ip做对比来判断ip是否发生了变化?
4.在一个处处受限的linux系统下,如何进行一系列的后续复杂操作?
- crontab 定时执行python脚本
关于crontab的用法可以参考这篇文章crontab 定时执行python脚本。通过crontab命令的一个重大好处是:机器重启之后,依然生效。
用到的主要是这两条命令:
1 | crontab -e #编写定时任务 |
每两分钟检查一次
在当前目录下,生成了log
- 获取实时的公网IP的方法
可以用如下代码:
1 | #!/usr/bin/python |
转换为python3的代码需要改动urllib.parse
- 判断公网ip是否变化
这个解决方法,并不能把第一次的ip给写死,因为路由器会经常重启,不能每次重启都要重新更改代码,把ip重新写死。这里的做法是在路由器中读取etc/ipsec.conf
的文件,这个文件中有当前路由的公网ip,而且当ip变化时,我们也需要更改这个文件。
1 | def getOriginIp(): |
通过这个OriginIp与当前的IP进行对比,如果有变化,需要去执行后面的一系列操作。
- 在一个处处受限的linux系统下,如何进行一系列的后续复杂操作?
Unifi路由器的linux系统处处受限,那后续的操作我就不放在路由器上了。于是,我就把后续的一些复杂点的操作移到了另外一台服务器上,路由器上的任务就变成了只负责检测ip变化,如果有变化,通过curl
命令去触发另一台服务器。
在另一台服务器上,搭建一台web服务器,我的愿景是尽量少下载第三方包,所以我并没有flask框架,而是用了python原生的一个web服务。大家可能之前听说python中有个内建的http服务器,只要通过这个命令就能开启(详情参见非常简单的PYTHON HTTP服务):
1 | python -m SimpleHTTPServer 8080 |
于是,我就找到了Python2自带的包BaseHTTPRequestHandler
,用法可以参考这篇文章Python BaseHTTPServer 介绍。我这里展示一个最简单的用法,通过crul http://localhost:8888
就可以执行HelloWorld函数。
1 | from BaseHTTPServer import BaseHTTPRequestHandler |
所以可以把一些后续操作,移动到另一台服务器上来。
动态DNS(DDNS)来解决动态IP
搭建外部访问公司的vpn通道是使用的pptp协议,因为我们的IP是动态IP,经常更换,所以一个比较好的解决办法是,把ip都解析到同一个域名下。这就引出了DDNS的概念。Unifi路由器配置的方法参见这个教程UniFi - 动态 DNS 配置
还有这个教程在usg里怎么填写动态DNS?。其中freedns是一个提供免费DDNS的网站,可以提供给你一个免费的域名来做DDNS的解析。在freedns注册登陆后,并选择好域名后,进入Dynamic DNS页面移动到底,会看到你的域名和ip地址。如果我想更改ip,可以点击quick
如果我想更改ip,可以点击quick
,进入到另一页面,其中提供的方法就是用wget把ip推送给freedns网站,来达到更改ip的效果。
所以每当检测到更换ip后,我们通过代码:
1 | os.system('wget -O http://freedns的地址') |
来更换域名和ip的绑定关系。
#####结尾
“有钱能使鬼推磨”,这是一句无比正确却毫无价值的一句话。直接多花些钱,当然可以升级成固定IP。但如果切换到上帝视角,就会发现,动态IP是很多企业和家庭所共同面临的一个问题。只是可能我平时上网需求,动态IP对我的上网没有任何影响。但如果当我们准备对网络搞些事情的时候,动态IP的问题就会铺面而来。所以我想如果不回避动态IP,而是想出一个解决办法,实际上是找出了一个对全行业都有价值的通用性解决方案。
参考链接:
极客时间| VPN:朝中有人好做官
v2ex|用 vpn 连上公司内网后无法访问内网其他服务器
阿里云VPN接口文档
阿里云 API 签名机制的 Python 实现
crontab 定时执行python脚本
非常简单的PYTHON HTTP服务
Python BaseHTTPServer 介绍
UniFi - 动态 DNS 配置
在usg里怎么填写动态DNS?