0x01 前言
我本来是使用DNSPod的DDNS服务,但自从我撤走极路由后就再也用不了了。同时DNSPod免费用户最低的TTL仅能设为600(10分钟),不足以满足DDNS的需求。我试过很多次重启服务器或者重新拨号后都无法在短时间内更新DNS的A记录。
其实DNSPod也有简单易用的API可供我编写python脚本,相对于阿里云年付的40.8元,DNSPod的年付360确实有点贵,同时DNSPod个人专业版的TTL最低也就300(5分钟),但阿里云最低却能设为1(1秒)。
如果你需要阿里云的付费服务,以下是一个9折优惠码:
阿里云9折优惠码:nfasn1


0x02 准备
因为受限于阿里云python SDK,这里使用python2.7进行编写。运行时请使用python2.x版本运行,而使用python3.x将会出现错误。
首先要在系统上安装阿里云的python SDK:
#新安装的系统没有python pip,所以先安装它和其他依赖包 [root@test ~]# yum install python-pip gcc gcc-devel autoconf automake python-devel python-pip python-crypto python-cryptography -y #安装alidns python DSK [root@test ~]# pip install aliyun-python-sdk-alidns
还需要准备以下账号内容:
- Access Key ID
- Access Key Secret
- 账号ID
准备好上面的内容后,将你的域名添加到阿里云解析DNS中:
完成后再在这个域名下添加一个主机A记录:
至此,准备工作已经完成。下面来获取域名的关键信息。
0x03 关键信息
通过脚本更新DNS记录需要几个关键的信息,如下:
- 一级域名(你的域名)
- 主机记录(你的二级域名)
- 记录类型(你的本地IP地址)
- 记录ID(这个解析记录的ID)
- 记录TTL(记录有效生存时间)
0x03.1 记录ID
其中1,2,3,5是可以确定的,而4则需要通过阿里云API获取:
def check_records(dns_domain):
clt = client.AcsClient(access_key_id, access_Key_secret, 'cn-hangzhou')
request = DescribeDomainRecordsRequest.DescribeDomainRecordsRequest()
request.set_DomainName(dns_domain)
request.set_accept_format(rc_format)
result = clt.do_action_with_exception(request)
result = result.decode()
result_dict = json.JSONDecoder().decode(result)
result_list = result_dict['DomainRecords']['Record']
for j in result_list:
print('Subdomain:' + j['RR'].encode() + ' ' + '| RecordId:' + j['RecordId'].encode())
return
返还的内容如下:
[root@web aliyun_ddns]# python aliyun_ddns.py Subdomain:subdomain_1 | RecordId:3331111111111111 Subdomain:subdomain_2 | RecordId:3331111111111111 Subdomain:subdomain_3 | RecordId:3331111111111111 Subdomain:subdomain_4 | RecordId:3331111111111111 Subdomain:subdomain_5 | RecordId:3331111111111111 Subdomain:subdomain_6 | RecordId:3331111111111111
0x03.2 本机IP
而获取本机IP,我选用ip.cn这个网站。当使用curl访问这个网站时,它会返还IP归属地和IP地址。使用脚本获取:
def my_ip_method_1():
get_ip_method = os.popen('curl -s ip.cn')
get_ip_responses = get_ip_method.readlines()[0]
get_ip_pattern = re.compile(r'\d+\.\d+\.\d+\.\d+')
get_ip_value = get_ip_pattern.findall(get_ip_responses)[0]
return get_ip_value
可能因为ip.cn这个站点受到攻击,他们的服务目前不太稳定,请使用my_ip_method_2或my_ip_method_3这两种本地IP的获取方式。
0x04 对比 | 更新
怎样才能知道IP地址是否有改变?
在获取本地IP后,再通过阿里云DNS API获取上一次的记录,两者相对比,如果不一致则更新DNS记录。
0x04.1 上一次的记录
def old_ip():
clt = client.AcsClient(access_key_id, access_Key_secret, 'cn-hangzhou')
request = DescribeDomainRecordInfoRequest.DescribeDomainRecordInfoRequest()
request.set_RecordId(rc_record_id)
request.set_accept_format(rc_format)
result = clt.do_action(request)
result = json.JSONDecoder().decode(result)
result = result['Value']
return result
0x04.2 更新记录
def update_dns(dns_rr, dns_type, dns_value, dns_record_id, dns_ttl, dns_format):
clt = client.AcsClient(access_key_id, access_Key_secret, 'cn-hangzhou')
request = UpdateDomainRecordRequest.UpdateDomainRecordRequest()
request.set_RR(dns_rr)
request.set_Type(dns_type)
request.set_Value(dns_value)
request.set_RecordId(dns_record_id)
request.set_TTL(dns_ttl)
request.set_accept_format(dns_format)
result = clt.do_action(request)
return result
0x04.3 对比
if rc_value_old == rc_value:
print 'The specified value of parameter Value is the same as old'
else:
update_dns(rc_rr, rc_type, rc_value, rc_record_id, rc_ttl, rc_format)
0x05 记录
我不但想要更新DDNS记录,我还想记录下每一次重新拨号后获取的IP,说不定日后能做个分析什么的。那么将记录写入文件:
def write_to_file():
time_now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
current_script_path = sys.path[7]
print current_script_path
log_file = current_script_path + '/' + 'aliyun_ddns_log.txt'
write = open(log_file, 'a')
write.write(time_now + ' ' + str(rc_value) + '\n')
write.close()
return
0x06 完整脚本
最新完整脚本请移步至GitHub:阿里云DDNS python脚本
# -*- coding: UTF-8 -*-
import json
import os
import re
import sys
from datetime import datetime
import requests
from aliyunsdkalidns.request.v20150109 import UpdateDomainRecordRequest, DescribeDomainRecordsRequest, \
DescribeDomainRecordInfoRequest
from aliyunsdkcore import client
access_key_id = ""
access_Key_secret = ""
# 请填写你的账号ID
account_id = ""
# 如果填写yes,则运行程序后仅显示域名信息,并不会更新记录,用于获取解析记录ID。
# 如果填写no,则运行程序后不显示域名信息,仅更新记录。
i_dont_know_record_id = 'yes'
# 请填写你的一级域名
rc_domain = ''
# 请填写你的解析记录
rc_rr = ''
# 请填写你的记录类型,DDNS请填写A,表示A记录
rc_type = 'A'
# 请填写解析记录ID
rc_record_id = ''
# 请填写解析有效生存时间TTL,单位:秒
rc_ttl = '30'
# 请填写返还内容格式,json,xml
rc_format = 'json'
def my_ip_method_1():
get_ip_method = os.popen('curl -s ip.cn')
get_ip_responses = get_ip_method.readlines()[0]
get_ip_pattern = re.compile(r'\d+\.\d+\.\d+\.\d+')
get_ip_value = get_ip_pattern.findall(get_ip_responses)[0]
return get_ip_value
def my_ip_method_2():
get_ip_method = os.popen('curl -s http://ip-api.com/json')
get_ip_responses = get_ip_method.readlines()[0]
get_ip_responses = eval(str(get_ip_responses))
get_ip_value = get_ip_responses['query']
return get_ip_value
def my_ip_method_3():
get_ip_method = requests.get('http://ifconfig.co/json').content
get_ip_value = eval(get_ip_method)
get_ip_value = get_ip_value['ip']
return get_ip_value
def check_records(dns_domain):
clt = client.AcsClient(access_key_id, access_Key_secret, 'cn-hangzhou')
request = DescribeDomainRecordsRequest.DescribeDomainRecordsRequest()
request.set_DomainName(dns_domain)
request.set_accept_format(rc_format)
result = clt.do_action_with_exception(request)
result = result.decode()
result_dict = json.JSONDecoder().decode(result)
result_list = result_dict['DomainRecords']['Record']
for j in result_list:
print('Subdomain:' + j['RR'].encode() + ' ' + '| RecordId:' + j['RecordId'].encode())
return
def old_ip():
clt = client.AcsClient(access_key_id, access_Key_secret, 'cn-hangzhou')
request = DescribeDomainRecordInfoRequest.DescribeDomainRecordInfoRequest()
request.set_RecordId(rc_record_id)
request.set_accept_format(rc_format)
result = clt.do_action_with_exception(request).decode()
result = json.JSONDecoder().decode(result)
result = result['Value']
return result
def update_dns(dns_rr, dns_type, dns_value, dns_record_id, dns_ttl, dns_format):
clt = client.AcsClient(access_key_id, access_Key_secret, 'cn-hangzhou')
request = UpdateDomainRecordRequest.UpdateDomainRecordRequest()
request.set_RR(dns_rr)
request.set_Type(dns_type)
request.set_Value(dns_value)
request.set_RecordId(dns_record_id)
request.set_TTL(dns_ttl)
request.set_accept_format(dns_format)
result = clt.do_action_with_exception(request)
return result
def write_to_file():
time_now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
current_script_path = sys.path[1]
log_file = current_script_path + '/' + 'aliyun_ddns_log.txt'
write = open(log_file, 'a')
write.write(time_now + ' ' + str(rc_value) + '\n')
write.close()
return
if i_dont_know_record_id == 'yes':
check_records(rc_domain)
elif i_dont_know_record_id == 'no':
rc_value = my_ip_method_3()
rc_value_old = old_ip()
if rc_value_old == rc_value:
print('The specified value of parameter Value is the same as old')
else:
print(update_dns(rc_rr, rc_type, rc_value, rc_record_id, rc_ttl, rc_format))
write_to_file()
可能因为ip.cn这个站点收到攻击,他们的服务目前不太稳定,请使用my_ip_method_2或my_ip_method_3这两种本地IP的获取方式。
修改第117行即可修改本地IP的获取方式。
0x07 运行
将程序通过crontab每分钟运行一次,请将脚本路径修改为你的实际路径:
*/1 * * * * root /usr/bin/python2.7 /usr/local/shell/aliyun_ddns.py > /dev/null 1>/dev/null
0x08 结语
其实这个脚本也可以更新其他类型的DNS记录,例如:CNAME,TXT等,只要知道解析记录ID即可。
经过近3天的运行,一切正常。























