基于python利用腾讯云API Explorer实现家用宽带的动态DNS解析

2021-12-06 11:19:01 浏览数 (1)

摘要

先介绍了应用背景:用来使得域名能够正确对应上动态IP。然后介绍了完成这项功能需要用到的API接口,并简单实现了对应的API接口调用框架。最后为了使用更加简洁,对程序逻辑作了进一步优化。实践证明真理就在实践中!

引言

总所周知,一般的家用宽带是很多条线路共用一个出口IP的。但是对与电信宽带来说,由于电信IP比较多,因此通过安装家用摄像头的方式借口来申请公网IP。但是为了防止用户使用ip在家里搭建不可描述的服务,电信给的公网IP一般都是动态IP,而且封掉了80,443,8080端口。根据笔者的观察来看,宽带的出口IP每三天换一次,即72小时换一次。同时,如果在三天内家里的光猫重启了,或者遭遇断电再来电的事故,IP地址也会变化。因此在使用家用宽带提供外网服务时,首先需要解决的是不断变化的IP地址的问题。

目前市面上已经存在很多的动态域名解析服务(DDNS),即根据你当前的IP地址,实时修改对应域名的在公共名字服务器上的A记录,使得用户在访问你的域名时能够正确达到你的服务器地址。比较有名气的有:

  1. 阿里DDNS(和本文原理一样的)
  2. 花生壳(内网穿透,通过第三方服务器进行内容交换)
  3. 3322(免费送二级域名)
  4. FreeDDns(免费送二级域名)
  5. WingDNS(功能超全!)
  6. Meibu(二级域名免费,顶级域名收费)

由市场调研可见,DDNS相关市场已经相当成熟,用户也趋于饱和,所以本文适合不想使用以上平台提供商的服务而是喜欢DIY瞎搞的同学。通过云 社区的搜索结果可以看到(如下图所示),社区目前还没有手把手教你实现动态域名解析的教程,所以本文主要的目的是使用python实现免费的动态域名解析能力,因为应用腾讯云的云API功能提高开发效率,所以云API这一开发神器值得进一步的推广。

云 社区云 社区

实现方法

材料准备

首先你需要有一个解析在腾讯云上的域名(我确实只有一个),如下图所示:

DNSPodDNSPod

然后点击右上角的头像,进入API密钥页面,如下图所示:

点击API密钥点击API密钥

在腾讯云API密钥界面找到APPID对应的IDKey即可,如下图所示:

创建DNSPod步骤创建DNSPod步骤

保存好你的密钥,以备后用!本文在所有出现SecretID的地方,统一使用ID代替;在所有出现密钥SecretKey的地方,统一使用Key代替。

全过程前提假定

本文所有的实验方法和实验结果均基于以下假定:

  1. 假定你当前的IP就是目前需要动态解析的IP
  2. 假定目标域名已经备案,不会被阻断访问
  3. 假定读者具有一定的编写python能力
  4. 假定读者对本文出现的名词都比较熟悉

基于API Explorer的DDNS请求框架

首先登陆腾讯云,查阅DNSPod的相关文档,文档地址:https://cloud.tencent.com/document/product/1427/56194。里面列举了一系列的相关的接口列表,我们可能需要用到以下相关的API接口,如下表所示:

API名称

描述

DescribeRecordList

获取域名的解析记录

CreateRecord

添加记录

ModifyRecord

修改记录

ModifyDynamicDNS

更新动态 DNS 记录

我的理解是,第三个接口是第一个接口和第二个接口的组合:先获取已有的解析列表,然后查找是否有相应的子域名存在解析记录,如果存在则对该子域名的记录值进行修改,如果不存在则增加一条新记录。下面对结合腾讯云API Explorer对第三个接口进行实现,云API Explorer的地址为:ModifyDynamicDNS。操作界面如下图所示:

API Explorer操作界面API Explorer操作界面

观察必需参数,发现有一项参数RecordId为待修改的记录ID,这项参数通过DescribeRecordList接口获取,如果我需要直接修改已有的记录时,记录需要修改的记录ID,后调用ModifyDynamicDNS接口;如果需要新建一个记录,并动态更新创建得到记录值时,可以先使用CreateRecord接口,记录创建好的记录ID,直接在ModifyDynamicDNS接口中使用。

本示例为动态修改已有的记录,因此结合DescribeRecordList接口和ModifyDynamicDNS接口实现域名动态解析能力。首先从DescribeRecordList接口文档页面进入API Explorer界面,配置好相关参数,如下图所示:

获取记录列表的接口获取记录列表的接口

配置好参数后,右侧会生成相应的请求代码,本例教程使用的是Python SDK,因此复制python代码到本地,新建一个DDNS.py文件,如下图所示:

API代码示例API代码示例

其中有涉及到腾讯云公共请求模块tencentcloud.common,该模块可以通过pip命令安装,文档地址:python SDK,安装命令如下:

代码语言:txt复制
$ pip install --upgrade tencentcloud-sdk-python

我们将上图第9行的"SecretId", "SecretKey"改为之前我们保存的IDKey,直接运行得到一大堆JSON,这里展示正常相应返回的Response对象的部分属性:

代码语言:txt复制
"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的记录为我们需要进行更新的记录,其对应得到RecordId760223447。我们保存这个值,以待备用。

然后如法炮制,将获得的RecordId代入到ModifyDynamicDNS接口中,该接口的API Explorer地址为:ModifyDynamicDNS如下图所示:

修改DNS修改DNS

经过测试发现SubDomain参数应该是必选项,如果不加此参数,该接口会默认修改@主机记录的参数值。将生成的代码复制到本地,去掉重复的模块导入剩下的部分如下图所示:

DDNS接口的本地代码DDNS接口的本地代码

将你的IDKey替换代码中的"SecretId", "SecretKey",直接运行代码,请求结果如下所示:

代码语言:txt复制
{
  "RecordId": 760223447,
  "RequestId": "2542afb0-bc7c-4c2e-b864-ca1d73795cdf"
}

回到DNSPod控制台查看API操作结果,如下图所示:

DDNS运行结果DDNS运行结果

可以看到该记录的值已经成功修改为了127.0.0.1,在实际应用中将该IP地址修改为其他IP地址即可,基于API Explorer的DDNS请求框架已经搭建好了,下面基于渐进式应用模式做进一步优化。

基于DDNS请求框架实现自动域名解析

由于当前的内容只有简单的框架,为了使它更加易用需要增加更多多内容。

自动获取指定子域名的RecordId

在上文中,我们是通过在大量的解析记录中肉眼查找需要修改的解析记录RecordId。但是这种方式的效率低下,这里实现根据请求结果,自动返回指定的子域名RecordId

实现逻辑:

  1. 申明变量,指定需要获取RecordId的主机记录值
  2. 遍历请求结果,找到Name与指定的主机记录相同的记录,返回RecordId
  3. 将模块函数化,便于调用

经修改后的获取域名的记录列表的代码改为如下所示:

代码语言:txt复制
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函数如下所示:

代码语言:txt复制
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或其他标准数据格式,如下所示:

代码语言:txt复制
当前IP: 59.52.217.194 归属地: 中国 江西 南昌

因此首先我们对该数据进行处理,提取IP地址。

然后,在获得IP地址后与先前的IP地址进行对比,判断IP是否发生变化,如果发生变化则将变动通过API提交。IP检查每隔一段时间运行一次,保证IP检测全方位无死角!这里用正则表达式和request模块完成IP提取方法,代码如下所示:

代码语言:txt复制
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分钟,实现代码如下图所示:

代码语言:txt复制
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文件中,其中主函数代码如下所示:

代码语言:txt复制
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请求的时候发生问题了,省了很多事儿,真不错!

0 人点赞