DDNS(Dynamic Domain Name Server,动态域名服务)是将用户的动态IP地址映射到一个固定的域名解析服务上
——百度百科
前段时间,我有个需求:家里 NAS 的 IP 不固定,而我需要一个域名,怎么样才能通过域名动态访问这个变化的 IP 呢?查了资料才知道,变化的 IP 和静态的域名作绑定,这就是 DDNS。看起来 DDNS 也不怎么高深。
目前提供 DDNS 服务的厂家非常多,比较注明的像 noip,花生壳 等等,有不少 OpenWRT 的路由器都有集成他们的服务,花生壳甚至有自己的硬件来解决 DDNS 和内网穿透的问题。但是如果要用他们的 DDNS 服务,就意味着需要使用他们的 DNS 服务。
我之前从腾讯云买了一个域名,所以打算直接腾讯云全家桶,直接用腾讯的 DNS 服务。腾讯云虽然没有现成的 DDNS 服务,但是提供了 DNS 解析记录查询、修改的接口,于是我觉得自己写一个小工具来解决 DDNS 的问题。
这个文档是针对使用了腾讯云 DNS 服务的用户准备的,当然如果你使用了阿里云,相关的操作就要遵循阿里云的文档了,但是道理其实是相通的。
可以先在平台上手动添加一个域名,并创建一个解析记录,当然也可以通过接口来创建,但是我觉得手动创建一条比接口创建还要快。
链接在这里
如果想快点看结论,这一步可以跳过,直接进入下一步。
相关说明主要来自于腾讯云的官方文档,大家可以跟着腾讯云的官方教程具体了解
我们在第一步中手动添加了域名,并且添加了一条解析记录,这条解析记录会有自己的 ID,我们需要获取这个 ID 才能通过接口来更新它。
import hmac # 用于签名加密,参考第二步中,腾讯 Signature 生成的说明
import base64 # 用于生成 base64 字符串,参考第二步中,腾讯 Signature 生成的说明
from hashlib import sha1 # 签名加密算法,参考第二步中,腾讯 Signature 生成的说明
from urllib.parse import urlencode, quote # url 编码相关操作,参考第二步中,腾讯 Signature 生成的说明
from urllib import request # 网络请求
from datetime import datetime # 用于公共参数中时间戳的生成
import json
# 生成时间戳
ts = str(int(datetime.timestamp(datetime.now())))
# 填写你自己的 Id、Key 和域名
secretId = 'AKIDixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
secretKey = 'ZLs9rxxxxxxxxxxxxxxxxxxxxxxxxxxx'
domain = 'abc.com'
subdomian = 'www'
# 请求地址和参数
baseUrl = 'cns.api.qcloud.com/v2/index.php?'
para = {
'Action': 'RecordList',
'Nonce': '18357',
'SecretId': secretId,
'Timestamp': ts,
'domain': domain,
'subDomain': subdomain
}
# 签名使用的原始字符串
sigSrcStr = 'GET' + baseUrl + urlencode(para)
# 经过签名加密和
para['Signature'] = base64.b64encode(hmac.new(secretKey.encode('utf-8'), sigSrcStr.encode('utf-8'), digestmod=sha1).digest())
url = 'https://' + baseUrl + urlencode(para, safe='')
response = request.urlopen(url).read().decode('utf-8')
result = json.loads(response)
print(result['data']['records'][0]['id'])
结果形如 555xxxxxx
,这一步只要做一次,查询到这个 ID 即可
通过 Python 获取外网 IP 地址,其实有两个思路:
第一种方案两行代码就能搞定
from urllib import request
IP = request.urlopen("https://api.ipify.org").read().decode('utf8')
第二种方法可以使用 Popen,相当于在命令行使用 ip a
或 ifconfig
的方法来查询;或者使用 Python 提供的 socket 服务。这篇文章 有非常详细的说明,代码是从 StackOverflow 上撸的
import socket
import fcntl
import struct
def get_ip_address(ifname):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
0x8915,
struct.pack('256s', ifname[:15]))[20:24])
IP = get_ip_address('eth0') # 这里的 eth0 需要改成你自己的外网网卡名,例如 ppp0、en0
通过
我们就可以通过腾讯云提供的接口来更新解析记录了。
import hmac
import base64
from hashlib import sha1
from urllib.parse import urlencode, quote
from urllib import request
from datetime import datetime
import socket
import json
def update_dns(IP):
ts = str(int(datetime.timestamp(datetime.now())))
# 填写你自己的 Id、Key 和域名
secretId = 'AKIDixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
secretKey = 'ZLs9rxxxxxxxxxxxxxxxxxxxxxxxxxxx'
domain = 'abc.com'
subdomian = 'www'
baseUrl = 'cns.api.qcloud.com/v2/index.php?'
# 请求的参数
para = {
'Action': 'RecordModify',
'Nonce': '18358',
'SecretId': secretId,
'Timestamp': ts,
'domain': domain,
'recordId': '555xxxxxx',
'recordLine': '默认',
'recordType': 'A', # "A","CNAME","MX","TXT","NS","AAAA","SRV"
'subDomain': subDomain,
'value': IP
}
sigSrcList = []
# urlencode 会对中文进行编码,导致验证失败,因此这里手动组装签名原始字符串
for k in para.keys():
sigSrcList.append(k + '=' + para[k])
sigSrcStr = 'GET' + baseUrl + '&'.join(sigSrcList)
para['Signature'] = base64.b64encode(hmac.new(secretKey.encode('utf-8'), sigSrcStr.encode('utf-8'), digestmod=sha1).digest())
url = 'https://' + baseUrl + urlencode(para, safe='')
response = request.urlopen(url)
print(response.read())
update_dns(IP)
以上是腾讯 DDNS 服务的基本思路,你还可以增加一些额外的判断,例如 IP 不变时,不对记录进行更新。
定期执行更新任务,不同系统提供的服务可能不同。在 Linux 和 Mac 上可以使用 crond 服务。
你需要通过 crontab 对定期任务进行编辑,可以参考这篇教程。
以上针对腾讯云的 DDNS 服务搭建提供了一个思路和方法,希望对大家有所帮助
参考文档
1 详解 Python 获取网卡 IP 地址 https://www.cnblogs.com/my_life/articles/9187714.html
2 Linux crontab 命令 https://www.runoob.com/linux/linux-comm-crontab.html