Implement a super simple dynamic domain name using Dnspod's API

There is a linux server at home. If you want to connect to it on the external network, you need a dynamic domain name service to obtain the IP of the home router. The options are:

1. Use free dynamic domain names such as peanut shells and log in in the router to use;

2. Check the external network IP of the home router from the Xiaomi router APP every time.

 

Some time ago, Peanut Shell required the real-name system, and it needed to be verified by holding an ID card photo, so it was abandoned; and every time it was too troublesome to look at the IP, I wanted to find other solutions, and suddenly I remembered that dnspod seems to have an api that can set a domain name Parsing and looking at it, the api of dnspod is very complete and completely feasible.

 

I don't want to do it too complicated, I don't have so much time, I use crontable for timing, and the shell script writes a simple logic. The principle is as follows: every 5 minutes, check whether the external network IP of the machine is consistent with the resolution address of its own domain name. If If it is inconsistent, call the api of dnspod to update the resolution of the domain name.

 

 It turns out that various versions have been implemented on the official website of dnspod. The shell version has long been written by someone. It can be seen that I have the same idea as the author. The original post address: https://blog.zengrong.net/post/1524.html , the last update was 2012-3-12, but it is no longer available. I made some modifications on the original basis :

1. Use the token method to call the interface, no longer need user name and password

2. If the existing dns resolution does not contain the detected second-level domain name, create a new resolution record (only updated before, skip if it does not exist)

3. The timing method is changed to crontable

 

Instructions:

1. Put the script anywhere, for example: /home/scripts/syncDomain.sh

2. Modify the configuration information such as token and domain name resolution in the script

3. Add execute permission, chmod +x /home/scripts/syncDomain.sh

4.crontable -e add a line

 

*/5 * * * * /home/scripts/syncDomain.sh > /dev/null 2>&1  

 

 

Log print location: /var/log/dnspodsh.log

shell code:

 

#!/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([email protected])"
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

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326066850&siteId=291194637