使用Dnspod的API实现一个超简略的动态域名
使用Dnspod的API实现一个超简单的动态域名
家中有个linux server,在外网想连接它的话,需要一个动态域名的服务来获取家里路由器的IP,可选的方案有:
1.使用花生壳等免费的动态域名,在路由器内登陆即可使用;
2.每次从小米路由APP上查看家中路由器的外网IP。
前段时间,花生壳要求实名制了,需要手持身份证照片验证,就弃用了;而每次看IP太麻烦了,就想找找其他解决方案,突然想起来dnspod好像有api能设定域名的解析,看了一下,dnspod的api很全,完全可行。
不想做的太复杂,没那么多时间,采用了crontable做定时,shell脚本写个简单的逻辑,原理如下:每隔5分钟检测一下本机的外网ip和自己域名的解析地址是否一致,如果不一致就调用dnspod的api更新域名的解析。
原来dnspod官网上已经有各种版本的实现,shell版本早就有人写了,可以看出来我和该作者的思路一样,原贴地址:https://blog.zengrong.net/post/1524.html,最后一次更新是2012-3-12,可是现在已经不能使用了,我在原来基础上做了一些修改:
1.使用token方式调用接口,不再需要用户名和密码
2.如果现有的dns解析不包含检测的二级域名,则新建一条解析记录(以前只更新,不存在则跳过)
3.定时方式改用了crontable
使用方法:
1.把脚本放在任意位置,例如:/home/scripts/syncDomain.sh
2.修改脚本中的token、解析域名等配置信息
3.添加执行权限,chmod +x /home/scripts/syncDomain.sh
4.crontable -e 增加一行
*/5 * * * * /home/scripts/syncDomain.sh > /dev/null 2>&1
日志打印位置:/var/log/dnspodsh.log
shell代码:
#!/bin/bash ############################## # dnspodsh v0.3 # 基于dnspod api构架的bash ddns客户端 # 作者:zrong(zengrong.net) # 详细介绍:http://zengrong.net/post/1524.htm # 创建日期:2012-02-13 # 更新日期:2012-03-11 # 修改日期:2017-12-18 # 修改详情:http://lief.iteye.com/blog/2404728 ############################## LOGIN_TOKEN="93132,8dfa9dsa98d8v9a9g9fa9a9gy9e9a9d7c3ce9" userAgent="dnspodsh/0.1(youremail@yourdomain.com)" apiUrl='https://dnsapi.cn/' ipUrl='http://members.3322.org/dyndns/getip' commonPost="login_token=$LOGIN_TOKEN&format=json&lang=cn" # 要处理的域名数组,每个元素代表一个域名的一组记录 # 在数组的一个元素中,以空格分隔域名和子域名 # 第一个空格前为主域名,后面用空格分离多个子域名 # 如果使用泛域名,必须用\*转义 domainList[0]='yourdomain.com www' domainList[1]='yourdomain2.com \* www' # logfile logDir='/var/log' logFile=$logDir'/dnspodsh.log' # 检测ip地址是否符合要求 checkip() { # ipv4地址 if [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]];then return 0 # ipv6地址 elif [[ "$1" =~ ^([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}$|^:((:[\da-fA-F]{1,4}){1,6}|:)$|^[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,5}|:)$|^([\da-fA-F]{1,4}:){2}((:[\da-fA-F]{1,4}){1,4}|:)$|^([\da-fA-F]{1,4}:){3}((:[\da-fA-F]{1,4}){1,3}|:)$|^([\da-fA-F]{1,4}:){4}((:[\da-fA-F]{1,4}){1,2}|:)$|^([\da-fA-F]{1,4}:){5}:([\da-fA-F]{1,4})?$|^([\da-fA-F]{1,4}:){6}:$ ]];then return 0 fi return 1 } getUrl() { curl -s -X POST -d $commonPost$2 -A $userAgent $apiUrl$1 } writeLog() { if [ -w $logDir ];then local pre="[`date '+%Y-%m-%d %H:%M:%S %A'`]" for arg in $@;do pre=$pre$arg done echo -e $pre>>$logFile fi echo -e $1 } getDomainList() { getUrl "Domain.List" "&type=all&offset=0&length=20" } # 根据域名id获取记录列表 # $1 域名id getRecordList() { getUrl "Record.List" "&domain_id=$1&offset=0&length=20" } # 设置记录 setRecord() { writeLog "set domain: $3.$8 to new ip:$7" local subDomain=$3 # 由于*会被扩展,在最后一步将转义的\*替换成* if [ "$subDomain" = '\*' ];then subDomain='*' fi local request="&domain_id=$1&sub_domain=$subDomain&record_type=$4&record_line=$5&ttl=$6&value=$7" local flag if [ $9 == 1 ] ; then flag="Record.Create" else request+="&record_id=$2" flag="Record.Modify" fi local saveResult=$(getUrl "$flag" "$request") # 检测返回是否正常,但即使不正常也不退出程序 if checkStatusCode "$saveResult" 0;then writeLog "set record: $3.$8 success." fi } # 设置一批记录 setRecords() { numRecord=${#changedRecords[@]} for (( i=0; i < $numRecord; i++ ));do setRecord ${changedRecords[$i]} done # 删除待处理的变量 unset changeRecords } # 通过key得到找到一个JSON对象字符串中的值 getDataByKey() { local s='s/{[^}]*"'$2'":["]*\('$(getRegexp $2)'\)["]*[^}]*}/\1/' #echo '拼合成的regexp:'$s echo $1|sed $s } # 根据key返回要获取的正则表达式 getRegexp() { case $1 in 'value') echo '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}';; 'type') echo '[A-Z]\+';; 'name') echo '[-_.A-Za-z*]\+';; 'ttl'|'id') echo '[0-9]\+';; 'line') echo '[^"]\+';; esac } # 通过一个JSON key名称,获取一个{}包围的JSON对象字符串 # $1 要搜索的key名称 # $2 要搜索的对应值 getJSONObjByKey() { grep -o '{[^}{]*"'$1'":"'$2'"[^}]*}' } # 获取A记录类型的域名信息 # 对于其它记录,同样的名称可以对应多条记录,因此使用getJSONObjByKey可能获取不到需要的数据 getJSONObjByARecord() { grep -o '{[^}{]*"name":"'$1'"[^}]*"type":"A"[^}]*}' } # 获取返回代码是否正确 # $1 要检测的字符串,该字符串包含{status:{code:1}}形式,代表DNSPodAPI返回正确 # $2 是否要停止程序,因为dnspod在代码错误过多的情况下会封禁账号 checkStatusCode() { if [[ "$1" =~ \{\"status\":\{[^}{]*\"code\":\"1\"[^}]*\} ]];then return 0 fi writeLog "DNSPOD return error:$1" # 根据参数需求退出程序 if [ -n "$2" ] && [ "$2" -eq 1 ];then writeLog 'exit dnspodsh' exit 1 fi } # 获取与当前ip不同的,要更新的记录的数组 getChangedRecords() { # 从DNSPod获取最新的域名列表 local domainListInfo=$(getDomainList) if [ -z "$domainListInfo" ];then writeLog 'DNSPOD tell me domain list is null,waiting...' return 1 fi checkStatusCode "$domainListInfo" 1 # 主域名的id local domainid local domainName # 主域名的JSON信息 local domainInfo # 主域名的所有记录列表 local recordList # 一条记录的JSON信息 local recordInfo # 记录的id local recordid local recordName # 记录的TTL local recordTtl # 记录的类型 local recordType # 记录的线路 local recordLine local j # 用于记录被改变的记录 unset changedRecords local numDomain=${#domainList[@]} local domainGroup for ((i=0;i<$numDomain;i++));do domainGroup=${domainList[$i]} j=0 for domain in ${domainGroup[@]};do # 列表的第一个项目,是主域名 if ((j==0));then domainName=$domain domainInfo=$(echo $domainListInfo|getJSONObjByKey 'name' $domainName) domainid=$(getDataByKey "$domainInfo" 'id') recordList=$(getRecordList $domainid) if [ -z "$recordList" ];then writeLog 'DNSPOD tell me record list null,waiting...' return 1 fi checkStatusCode "$recordList" 1 else # 从dnspod获取要设置的子域名记录的信息 recordInfo=$(echo $recordList|getJSONObjByARecord $domain) # 如果取不到记录,则新增域名解析 oldip="" needCreate=0 if [ -z "$recordInfo" ];then writeLog "设定的域名在现有解析中不存在$domain" needCreate=1 recordid="no_id" recordTtl=600 recordType="A" else # 从dnspod获取要设置的子域名的ip oldip=$(getDataByKey "$recordInfo" 'value') recordid=$(getDataByKey "$recordInfo" 'id') recordName=$(getDataByKey "$recordInfo" 'name') recordTtl=$(getDataByKey "$recordInfo" 'ttl') recordType=$(getDataByKey "$recordInfo" 'type') fi if [ "$newip" != "$oldip" ];then recordLine='默认' # 这里一共有9个参数,与setRecord中的参数对应 changedRecords[${#changedRecords[@]}]="$domainid $recordid $domain $recordType $recordLine $recordTtl $newip $domainName $needCreate" fi fi j=$((j+1)) done done } # 执行检测工作 go() { #writeLog "checking..." # 由于获取到的数据多了一些多余的字符,所以提取ip地址的部分 # 从api中获取当前的外网ip newip=$(curl -s $ipUrl|grep -o $(getRegexp 'value')) # 如果获取最新ip错误,就继续等待下一次取值 if ! checkip "$newip";then writeLog 'can not get new ip,exit...' exit 1 fi # 获取需要修改的记录 getChangedRecords if (( ${#changedRecords[@]} > 0 ));then setRecords writeLog "ip is changed,new ip is:$newip" fi } go