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天的运行,一切正常。