摘要
先介绍了应用背景:用来使得域名能够正确对应上动态IP。然后介绍了完成这项功能需要用到的API接口,并简单实现了对应的API接口调用框架。最后为了使用更加简洁,对程序逻辑作了进一步优化。实践证明真理就在实践中!
引言
总所周知,一般的家用宽带是很多条线路共用一个出口IP的。但是对与电信宽带来说,由于电信IP比较多,因此通过安装家用摄像头的方式借口来申请公网IP。但是为了防止用户使用ip在家里搭建不可描述的服务,电信给的公网IP一般都是动态IP,而且封掉了80,443,8080
端口。根据笔者的观察来看,宽带的出口IP每三天换一次,即72小时换一次。同时,如果在三天内家里的光猫重启了,或者遭遇断电再来电的事故,IP地址也会变化。因此在使用家用宽带提供外网服务时,首先需要解决的是不断变化的IP地址的问题。
目前市面上已经存在很多的动态域名解析服务(DDNS),即根据你当前的IP地址,实时修改对应域名的在公共名字服务器上的A记录,使得用户在访问你的域名时能够正确达到你的服务器地址。比较有名气的有:
- 阿里DDNS(和本文原理一样的)
- 花生壳(内网穿透,通过第三方服务器进行内容交换)
- 3322(免费送二级域名)
- FreeDDns(免费送二级域名)
- WingDNS(功能超全!)
- Meibu(二级域名免费,顶级域名收费)
由市场调研可见,DDNS相关市场已经相当成熟,用户也趋于饱和,所以本文适合不想使用以上平台提供商的服务而是喜欢DIY瞎搞的同学。通过云+社区的搜索结果可以看到(如下图所示),社区目前还没有手把手教你实现动态域名解析的教程,所以本文主要的目的是使用python实现免费的动态域名解析能力,因为应用腾讯云的云API功能提高开发效率,所以云API这一开发神器值得进一步的推广。
实现方法
材料准备
首先你需要有一个解析在腾讯云上的域名(我确实只有一个),如下图所示:
然后点击右上角的头像,进入API密钥
页面,如下图所示:
在腾讯云API密钥界面找到APPID
对应的ID
和Key
即可,如下图所示:
保存好你的密钥,以备后用!本文在所有出现SecretID
的地方,统一使用ID
代替;在所有出现密钥SecretKey
的地方,统一使用Key
代替。
全过程前提假定
本文所有的实验方法和实验结果均基于以下假定:
- 假定你当前的IP就是目前需要动态解析的IP
- 假定目标域名已经备案,不会被阻断访问
- 假定读者具有一定的编写python能力
- 假定读者对本文出现的名词都比较熟悉
基于API Explorer的DDNS请求框架
首先登陆腾讯云,查阅DNSPod的相关文档,文档地址:https://cloud.tencent.com/document/product/1427/56194。里面列举了一系列的相关的接口列表,我们可能需要用到以下相关的API接口,如下表所示:
API名称 | 描述 |
---|---|
DescribeRecordList | 获取域名的解析记录 |
CreateRecord | 添加记录 |
ModifyRecord | 修改记录 |
ModifyDynamicDNS | 更新动态 DNS 记录 |
我的理解是,第三个接口是第一个接口和第二个接口的组合:先获取已有的解析列表,然后查找是否有相应的子域名存在解析记录,如果存在则对该子域名的记录值进行修改,如果不存在则增加一条新记录。下面对结合腾讯云API Explorer对第三个接口进行实现,云API Explorer的地址为:ModifyDynamicDNS。操作界面如下图所示:
观察必需参数,发现有一项参数RecordId
为待修改的记录ID,这项参数通过DescribeRecordList
接口获取,如果我需要直接修改已有的记录时,记录需要修改的记录ID,后调用ModifyDynamicDNS
接口;如果需要新建一个记录,并动态更新创建得到记录值时,可以先使用CreateRecord
接口,记录创建好的记录ID,直接在ModifyDynamicDNS
接口中使用。
本示例为动态修改已有的记录,因此结合DescribeRecordList
接口和ModifyDynamicDNS
接口实现域名动态解析能力。首先从DescribeRecordList接口文档页面进入API Explorer界面,配置好相关参数,如下图所示:
配置好参数后,右侧会生成相应的请求代码,本例教程使用的是Python SDK
,因此复制python
代码到本地,新建一个DDNS.py
文件,如下图所示:
其中有涉及到腾讯云公共请求模块tencentcloud.common
,该模块可以通过pip
命令安装,文档地址:python SDK,安装命令如下:
$ pip install --upgrade tencentcloud-sdk-python
我们将上图第9行的"SecretId", "SecretKey"
改为之前我们保存的ID
和Key
,直接运行得到一大堆JSON,这里展示正常相应返回的Response
对象的部分属性:
"Response": {
"RequestId": "a3bee0a2-bac6-43db-b76d-a4ce11cec0c7",
"RecordCountInfo": {
"SubdomainCount": 23,
"TotalCount": 23,
"ListCount": 23
},
"RecordList": [
{
"Value": "59.52.241.247",
"Status": "ENABLE",
"UpdatedOn": "2021-08-13 20:03:43",
"Name": "homesource",
"Line": "默认",
"LineId": "0",
"Type": "A",
"Weight": null,
"MonitorStatus": "",
"Remark": "",
"RecordId": 760223447,
"TTL": 600,
"MX": 0
}
]
}
其中名字为homesource
的记录为我们需要进行更新的记录,其对应得到RecordId
为760223447
。我们保存这个值,以待备用。
然后如法炮制,将获得的RecordId
代入到ModifyDynamicDNS
接口中,该接口的API Explorer地址为:ModifyDynamicDNS如下图所示:
经过测试发现SubDomain
参数应该是必选项,如果不加此参数,该接口会默认修改@
主机记录的参数值。将生成的代码复制到本地,去掉重复的模块导入剩下的部分如下图所示:
将你的ID
和Key
替换代码中的"SecretId", "SecretKey"
,直接运行代码,请求结果如下所示:
{
"RecordId": 760223447,
"RequestId": "2542afb0-bc7c-4c2e-b864-ca1d73795cdf"
}
回到DNSPod
控制台查看API操作结果,如下图所示:
可以看到该记录的值已经成功修改为了127.0.0.1
,在实际应用中将该IP地址修改为其他IP地址即可,基于API Explorer的DDNS请求框架已经搭建好了,下面基于渐进式应用模式做进一步优化。
基于DDNS请求框架实现自动域名解析
由于当前的内容只有简单的框架,为了使它更加易用需要增加更多多内容。
自动获取指定子域名的RecordId
在上文中,我们是通过在大量的解析记录中肉眼查找需要修改的解析记录RecordId
。但是这种方式的效率低下,这里实现根据请求结果,自动返回指定的子域名RecordId
。
实现逻辑:
- 申明变量,指定需要获取
RecordId
的主机记录值 - 遍历请求结果,找到
Name
与指定的主机记录相同的记录,返回RecordId
- 将模块函数化,便于调用
经修改后的获取域名的记录列表的代码改为如下所示:
def ReturnRecordId(Domain, SubDomain): try: cred = credential.Credential(SecretId, SecretKey) httpProfile = HttpProfile() httpProfile.endpoint = "dnspod.tencentcloudapi.com"
clientProfile = ClientProfile() clientProfile.httpProfile = httpProfile client = dnspod_client.DnspodClient(cred, "", clientProfile) req = models.DescribeRecordListRequest() params = { "Domain": Domain } req.from_json_string(json.dumps(params)) resp = client.DescribeRecordList(req) for record in resp.RecordList: if record.Name == SubDomain: return record.RecordId print("未找到对应的记录值,请先创建相应的主机记录!") return -2 except TencentCloudSDKException as err: print("获取域名的记录列表失败,请重试!") return -1</code></pre></div></div><h4 id="77u2n" name="ModifyDynamicDNS%E6%8E%A5%E5%8F%A3%E5%B0%81%E8%A3%85%E6%88%90%E5%87%BD%E6%95%B0%EF%BC%8C%E4%BE%BF%E4%BA%8E%E8%B0%83%E7%94%A8">ModifyDynamicDNS接口封装成函数,便于调用</h4><p>封装好的<code>ModifyDynamicDNS</code>函数如下所示:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">def ModifyDynamicDNS(RecordId, Domain ,SubDomain, Ip): try: cred = credential.Credential(SecretId, SecretKey) httpProfile = HttpProfile() httpProfile.endpoint = "dnspod.tencentcloudapi.com" clientProfile = ClientProfile() clientProfile.httpProfile = httpProfile client = dnspod_client.DnspodClient(cred, "", clientProfile) req = models.ModifyDynamicDNSRequest() params = { "Domain": Domain, "SubDomain": SubDomain, "RecordId": RecordId, "RecordLine": "默认", "Value": Ip } req.from_json_string(json.dumps(params)) resp = client.ModifyDynamicDNS(req) if str(RecordId) in resp.to_json_string(): print("更新成功!") return 1 except TencentCloudSDKException as err: return 0</code></pre></div></div><h4 id="bf799" name="%E5%AE%9A%E6%97%B6%E8%8E%B7%E5%8F%96%E5%BD%93%E5%89%8D%E7%9A%84IP%E5%9C%B0%E5%9D%80">定时获取当前的IP地址</h4><p>实现动态DNS解析,首先要获得当前本地的公网IP,然后将改IP提交到相应的API中。目前有很多免费公共的本地IP查询接口,这里我们选择的是:<code>https://ip.tool.lu/</code>,这个网站返回的结果更快,但是其返回的结果不是标准的JSON或其他标准数据格式,如下所示:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">当前IP: 59.52.217.194 归属地: 中国 江西 南昌</code></pre></div></div><p>因此首先我们对该数据进行处理,提取IP地址。</p><p>然后,在获得IP地址后与先前的IP地址进行对比,判断IP是否发生变化,如果发生变化则将变动通过API提交。IP检查每隔一段时间运行一次,保证IP检测全方位无死角!这里用正则表达式和request模块完成IP提取方法,代码如下所示:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">import requests
import re
def GetCurrentIP():
resp = requests.get('https://ip.tool.lu/').content
resp = resp.decode('utf8')
IPPattern = '\d+.\d+.\d+.\d+'
matchObj = re.search(IPPattern, resp)
return matchObj.group()
ip = GetCurrentIP()
每隔一段时间检查一下当前的IP是否与之前的IP相同,这里指定的时间间隔为10分钟,实现代码如下图所示:
import time
interval = 600 # 每10分钟检查一次IP
OldIP = ""
while True:
CurrentIP = GetCurrentIP()
if OldIP != CurrentIP:
res = ModifyDynamicDNS(RecordId=RecordId, Domain=Domain, SubDomain=SubDomain, Ip = CurrentIP)
if res:
print(f'IP成功更新!原IP:{OldIP},新IP:{CurrentIP}')
OldIP = CurrentIP
else:
print('动态域名解析API出问题了,正在重试!')
continue
time.sleep(interval)
由该逻辑可以看出,当程序的第一次运行时,原IP是不显示的,但是当IP发生修改后,原IP就能正常显示。整理以上的IP更新逻辑,将其更新到DDNS.py
文件中,其中主函数代码如下所示:
if __name__ == "__main__":
SecretId = ""
SecretKey = ""
Domain = "eatrice.cn" # 主域名
SubDomain = "homesource" # 指定要修改的子域名
interval = 600 # 每10分钟检查一次IP
RecordId = ReturnRecordId(Domain=Domain, SubDomain=SubDomain)
if RecordId == -1:
print("RecordList请求发生问题!")
exit()
if RecordId == -2:
print("没有找到你要的子域名,请先新建一个!")
OldIP = ""
while True:
CurrentIP = GetCurrentIP()
if OldIP != CurrentIP:
res = ModifyDynamicDNS(RecordId=RecordId, Domain=Domain, SubDomain=SubDomain, Ip = CurrentIP)
if res:
print(f'IP成功更新!原IP:{OldIP},新IP:{CurrentIP}')
OldIP = CurrentIP
else:
print('动态域名解析API出问题了,正在重试!')
continue
time.sleep(interval)
至此,基于API Explorer的本地实现动态域名解析的教程已经全部完成。完整源代码已经开源至GitHub
,地址:QiQiWan/PythonDDNS
小结
家用IP是动态的确实比较烦人,但是可以结合DNSPod实现曲线救国,实践证明,办法总比困难的多!另外值得一提的是API Explorer
真是神器,妈妈再也不用担心在做API请求的时候发生问题了,省了很多事儿,真不错!