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
DNSPod DNS费用详情
DNSPod DNS费用详情
阿里云DNS费用详情
阿里云DNS费用详情

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

还需要准备以下账号内容:

  1. Access Key ID
  2. Access Key Secret
  3. 账号ID

准备好上面的内容后,将你的域名添加到阿里云解析DNS中:

WechatIMG23

完成后再在这个域名下添加一个主机A记录:

WechatIMG24

至此,准备工作已经完成。下面来获取域名的关键信息。

0x03 关键信息

通过脚本更新DNS记录需要几个关键的信息,如下:

  1. 一级域名(你的域名)
  2. 主机记录(你的二级域名)
  3. 记录类型(你的本地IP地址)
  4. 记录ID(这个解析记录的ID)
  5. 记录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地址。使用脚本获取:

WechatIMG25

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