摘要
先介绍了应用背景: 用来使得域名能够正确对应上动态 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 这一开发神器值得进一步的推广.
云 + 社区
实现方法
材料准备
首先你需要有一个解析在腾讯云上的域名 (我确实只有一个), 如下图所示:
DNSPod
然后点击右上角的头像, 进入 API 密钥页面, 如下图所示:
点击 API 密钥
在腾讯云 API 密钥界面找到 APPID 对应的 ID 和 Key 即可, 如下图所示:
创建 DNSPod 步骤
保存好你的密钥, 以备后用! 本文在所有出现 SecretID 的地方, 统一使用 ID 代替; 在所有出现密钥 SecretKey 的地方, 统一使用 Key 代替.
全过程前提假定
本文所有的实验方法和实验结果均基于以下假定:
假定你当前的 IP 就是目前需要动态解析的 IP
假定目标域名已经备案, 不会被阻断访问
假定读者具有一定的编写 python 能力
假定读者对本文出现的名词都比较熟悉
基于 API Explorer 的 DDNS 请求框架
首先登陆腾讯云, 查阅 DNSPod 的相关文档, 文档地址:. 里面列举了一系列的相关的接口列表, 我们可能需要用到以下相关的 API 接口, 如下表所示:
API 名称 | 描述 |
---|---|
获取域名的解析记录 | |
添加记录 | |
修改记录 | |
更新动态 DNS 记录 |
我的理解是, 第三个接口是第一个接口和第二个接口的组合: 先获取已有的解析列表, 然后查找是否有相应的子域名存在解析记录, 如果存在则对该子域名的记录值进行修改, 如果不存在则增加一条新记录. 下面对结合腾讯云 API Explorer 对第三个接口进行实现, 云 API Explorer 的地址为:. 操作界面如下图所示:
API Explorer 操作界面
观察必需参数, 发现有一项参数 RecordId 为待修改的记录 ID, 这项参数通过 DescribeRecordList 接口获取, 如果我需要直接修改已有的记录时, 记录需要修改的记录 ID, 后调用 ModifyDynamicDNS 接口; 如果需要新建一个记录, 并动态更新创建得到记录值时, 可以先使用 CreateRecord 接口, 记录创建好的记录 ID, 直接在 ModifyDynamicDNS 接口中使用.
本示例为动态修改已有的记录, 因此结合 DescribeRecordList 接口和 ModifyDynamicDNS 接口实现域名动态解析能力. 首先从 DescribeRecordList 接口文档页面进入 API Explorer 界面, 配置好相关参数, 如下图所示:
获取记录列表的接口
配置好参数后, 右侧会生成相应的请求代码, 本例教程使用的是 Python SDK, 因此复制 python 代码到本地, 新建一个 DDNS.py 文件, 如下图所示:
API 代码示例
其中有涉及到腾讯云公共请求模块 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 地址为: 如下图所示:
修改 DNS
经过测试发现 SubDomain 参数应该是必选项, 如果不加此参数, 该接口会默认修改 @主机记录的参数值. 将生成的代码复制到本地, 去掉重复的模块导入剩下的部分如下图所示:
DDNS 接口的本地代码
将你的 ID 和 Key 替换代码中的 "SecretId", "SecretKey", 直接运行代码, 请求结果如下所示:
- {
- "RecordId": 760223447,
- "RequestId": "2542afb0-bc7c-4c2e-b864-ca1d73795cdf"
- }
回到 DNSPod 控制台查看 API 操作结果, 如下图所示:
DDNS 运行结果
可以看到该记录的值已经成功修改为了 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
ModifyDynamicDNS 接口封装成函数, 便于调用
封装好的 ModifyDynamicDNS 函数如下所示:
- 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
定时获取当前的 IP 地址
实现动态 DNS 解析, 首先要获得当前本地的公网 IP, 然后将改 IP 提交到相应的 API 中. 目前有很多免费公共的本地 IP 查询接口, 这里我们选择的是: https://ip.tool.lu/, 这个网站返回的结果更快, 但是其返回的结果不是标准的 JSON 或其他标准数据格式, 如下所示:
当前 IP: 59.52.217.194 归属地: 中国 江西 南昌
因此首先我们对该数据进行处理, 提取 IP 地址.
然后, 在获得 IP 地址后与先前的 IP 地址进行对比, 判断 IP 是否发生变化, 如果发生变化则将变动通过 API 提交. IP 检查每隔一段时间运行一次, 保证 IP 检测全方位无死角! 这里用正则表达式和 request 模块完成 IP 提取方法, 代码如下所示:
- 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, 地址: https://github.com/QiQiWan/PythonDDNS
小结
家用 IP 是动态的确实比较烦人, 但是可以结合 DNSPod 实现曲线救国, 实践证明, 办法总比困难的多! 另外值得一提的是 API Explorer 真是神器, 妈妈再也不用担心在做 API 请求的时候发生问题了, 省了很多事儿, 真不错!
来源: https://www.qcloud.com/developer/article/1897406