Skip to content

Instantly share code, notes, and snippets.

@liuluo1979
Last active June 22, 2019 17:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save liuluo1979/01ceb2a53bcf1cb467f70c341181265e to your computer and use it in GitHub Desktop.
Save liuluo1979/01ceb2a53bcf1cb467f70c341181265e to your computer and use it in GitHub Desktop.
[dnspodshMOD] 修改自zrong(zengrong.net)的dnspodsh(https://gist.github.com/zrong/1822396)
*/30 * * * * bash /YourPath/dnspod.sh > /dev/null
##############################
# dnspodshMOD config
##############################
# format: "id,token"
# for example: login_token="12345,666666abcd666666abcd666666abcd66"
login_token=""
wan_iface_ipv4='pppoe-vwan1'
wan_iface_ipv6='wan6'
ddns_ipv4=1
ddns_ipv6=0
# multiple subdomains separated by space
# warn: the wildcard record name * must escape(convert) to \*
# for example: domainList[0]='abc.com @ \* ow'
domainList[0]=''
domainList[1]=''
#logDir=''
#logMaxLines=100
#delay=5
#retry_count=1
#defaultRecordLineId='0'
# Warn: if record_line_id is not '0', must escape '=' to '%3D'
# line_ids:
# "默认": 0
# "国内": "7=0"
# "国外": "3=0"
# "电信": "10=0"
# "联通": "10=1"
# "教育网": "10=2"
# "移动": "10=3"
# 国内
#ipv4Url='http://myip.ipip.net'
# 国外
#ipv4Url='http://v4.ipv6-test.com/api/myip.php'
#ipv4Url='http://v4.ip.zxinc.org/getip'
#ipv4Url='http://ipecho.net/plain'
#ipv4Url='http://whatismyip.akamai.com'
#ipv4Url='http://ipv4.myexternalip.com/raw'
#ipv4Url='http://myexternalip.com/raw'
#ipv4Url='http://icanhazip.com'
#ipv4Url='http://ip.sb'
#ipv6Url='http://v6.ipv6-test.com/api/myip.php'
#ipv6Url='http://v6.ip.zxinc.org/getip'
#ipv6Url='http://ipv6.myexternalip.com/raw'
#ipv6Url='http://myexternalip.com/raw'
#ipv6Url='http://ip.sb'
#apiUrl='https://dnsapi.cn/'
#addon_opt='-4'
#!/bin/bash
##############################
# dnspodshMOD v0.4
# Modified By shaun
##############################
# dnspodsh v0.3
# bash ddns client based on dnspod api
# Author: zrong(zengrong.net)
# Detailed introduction: http://zengrong.net/post/1524.htm
# Created: 2012-02-13
# Last Revision Date: 2012-03-11
##############################
modVERSION=0.4
userAgent="dnspodshMOD/${modVERSION}(shaun79@gmail.com)"
getipv4()
{
if [ -n "$wan_iface_ipv4" ]; then
newipv4=`$IP -4 addr show $wan_iface_ipv4|grep inet|grep -v '127.\d\{1,3\}.\d\{1,3\}.\d\{1,3\}\|169.254.\d\{1,3\}.\d\{1,3\}\|10.\d\{1,3\}.\d\{1,3\}.\d\{1,3\}\|172.\(1[6-9]\|2\d\|3[01]\).\d\{1,3\}.\d\{1,3\}\|192.168.\d\{1,3\}.\d\{1,3\}'|awk '{print $2}'|cut -d'/' -f1`
else
[ -z "$ipv4Url" ] && writeLog "ERROR...please check \"wan_iface_ipv4\" or \"ipv4Url\" in \"$config_file\"" && exit 1
# regular expression examples
#newipv4=`$CURL $addon_opt -sL $ipv4Url | grep -Eo '\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b'`
#newipv4=`$CURL $addon_opt -sL $ipv4Url | grep -Eo "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"`
IPV4_REGEX="[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}"
newipv4=`$CURL $addon_opt -sL $ipv4Url | grep -m 1 -o "$IPV4_REGEX"`
fi
}
getipv6()
{
if [ -n "$wan_iface_ipv6" ]; then
newipv6=`$IP -6 addr show $wan_iface_ipv6|grep inet6|grep -v '::1\|fe80::\|fc00::\|fd00::'|awk '{print $2}'|cut -d'/' -f1`
else
[ -z "$ipv6Url" ] && writeLog "ERROR...please check \"wan_iface_ipv6\" or \"ipv6Url\" in \"$config_file\"" && exit 1
# regular expression examples
#newipv6=`$CURL $addon_opt -sL $ipv6Url | grep -Eo "\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*"`
IPV6_REGEX="\(\([0-9A-Fa-f]\{1,4\}:\)\{1,\}\)\(\([0-9A-Fa-f]\{1,4\}\)\{0,1\}\)\(\(:[0-9A-Fa-f]\{1,4\}\)\{1,\}\)"
newipv6=`$CURL $addon_opt -sL $ipv6Url | grep -m 1 -o "$IPV6_REGEX"`
fi
}
getip()
{
[ $ENABLE_IPv4 -eq 1 ] && getipv4
[ $ENABLE_IPv6 -eq 1 ] && getipv6
}
checkip()
{
# ipv4
#if [ $ENABLE_IPv4 -eq 1 ] && [[ "$1" =~ ^((2[0-4][0-9]|25[0-5]|1[0-9][0-9]|[1-9]?[0-9])(\.(2[0-4][0-9]|25[0-5]|1[0-9][0-9]|[1-9]?[0-9])){3})$ ]];then
if [ $ENABLE_IPv4 -eq 1 ] && [[ "$1" =~ ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]];then
return 0
# ipv6
elif [ $ENABLE_IPv6 -eq 1 ] && [[ "$1" =~ ^(::|(([a-fA-F0-9]{1,4}):){7}(([a-fA-F0-9]{1,4}))|(:(:([a-fA-F0-9]{1,4})){1,6})|((([a-fA-F0-9]{1,4}):){1,6}:)|((([a-fA-F0-9]{1,4}):)(:([a-fA-F0-9]{1,4})){1,6})|((([a-fA-F0-9]{1,4}):){2}(:([a-fA-F0-9]{1,4})){1,5})|((([a-fA-F0-9]{1,4}):){3}(:([a-fA-F0-9]{1,4})){1,4})|((([a-fA-F0-9]{1,4}):){4}(:([a-fA-F0-9]{1,4})){1,3})|((([a-fA-F0-9]{1,4}):){5}(:([a-fA-F0-9]{1,4})){1,2})) ]];then
return 0
fi
return 1
}
getUrl()
{
#$CURL -s -A $userAgent -d $commonPost$2 --trace $traceFile $apiUrl$1
$CURL $addon_opt -sL -A $userAgent -d $commonPost$2 $apiUrl$1
}
getVersion()
{
getUrl "Info.Version"
}
getUserDetail()
{
getUrl "User.Detail"
}
writeLog()
{
local curLineCount delCount
if [ -w $logDir ];then
curLineCount=`wc -l $logFile|awk '{print $1}'`
if [ ${curLineCount} -gt ${logMaxLines} ];then
delCount=`expr ${curLineCount} - ${logMaxLines} + ${logMaxLines} / 2`
sed -i "1,${delCount}d" $logFile
fi
local pre=`date`
for arg in $@;do
pre=$pre'\t'$arg
done
echo -e $pre>>$logFile
fi
echo -e $1
}
save_conf()
{
grep "$1" $config_file >/dev/null 2>&1 || { echo "$1=$2" >> $config_file; return 0; }
sed -i "s/$1=.*/$1=$2/g" $config_file
}
getDomainList()
{
getUrl "Domain.List" "&type=all&offset=0&length=10"
}
getRecordList()
{
getUrl "Record.List" "&domain_id=$1&offset=0&length=20"
}
setRecord()
{
writeLog "set domain $3.$8 to new ip:$7"
local subDomain=$3
local request saveResult
# recover the escaped wildcard record name
if [ "$subDomain" = '\*' ];then
subDomain='*'
fi
# Only Record.Modify can set ipv6(AAAA) record. Record.Ddns can not.
# Update record with Record.Ddns will change record ttl to 10s (still don't know why).
# So use Record.Ddns for ipv4(A) record and Record.Modify for ipv6(AAAA) record.
if [ "$4" = 'AAAA' ];then
request="&domain_id=$1&record_id=$2&sub_domain=$subDomain&record_type=$4&record_line_id=$5&ttl=$6&value=$7"
saveResult=$(getUrl 'Record.Modify' "$request")
fi
if [ "$4" = 'A' ];then
request="&domain_id=$1&record_id=$2&sub_domain=$subDomain&record_line_id=$5&value=$7"
saveResult=$(getUrl 'Record.Ddns' "$request")
fi
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
}
createRecord()
{
writeLog "create subDomain $2.$6 to new ip:$5"
# recover the escaped wildcard record name
local subDomain=$2
if [ "$subDomain" = '\*' ];then
subDomain='*'
fi
local request="&domain_id=$1&sub_domain=$subDomain&record_type=$3&record_line_id=$4&value=$5"
local saveResult=$(getUrl 'Record.Create' "$request")
if checkStatusCode "$saveResult" 0;then
writeLog "create record $2.$6 success."
fi
}
createRecords()
{
numRecord=${#nonExistentRecords[@]}
for (( i=0; i < $numRecord; i++ ));do
createRecord ${nonExistentRecords[$i]}
done
unset nonExistentRecords
}
createDomain()
{
writeLog "do create domain $1"
local request="&domain=$1&group_id=5"
local saveResult=$(getUrl 'Domain.Create' "$request")
if checkStatusCode "$saveResult" 0;then
writeLog "create domain $1 success."
fi
}
getDataByKey()
{
#local s='s/{[^}]*"'$2'":["]*\('$(getRegexp $2)'\)["]*[^}]*}/\1/'
local s='s/{[^}]*"'$2'":["]*\([^",]*\)["]*[^}]*}/\1/'
echo $1|sed $s
}
getRegexp()
{
case $1 in
'value') echo '[a-fA-F0-9.:]\+';;
'type') echo '[A-Z]\+';;
'name') echo '[-_.A-Za-z@*]\+';;
'ttl'|'id') echo '[0-9]\+';;
'line') echo '[^"]\+';;
'line_id') echo '[0-9]\+';;
esac
}
getJSONObjByKey()
{
grep -o '{[^}{]*"'$1'":"'$2'"[^}]*}'
}
getJSONObjBySubDomainRecordType()
{
grep -o '{[^}{]*"name":"'$1'"[^}]*"type":"'$2'"[^}]*}'
}
# {status:{code:1}} means success
# DNSPod will block the account if there are too many code errors
# So if the second input parameter is '1' then stop the dnspodsh script
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
}
getChangedRecords()
{
local domainListInfo=$(getDomainList)
if [ -z "$domainListInfo" ];then
writeLog 'DNSPOD tell me domain list is null,waiting...'
return 1
fi
checkStatusCode "$domainListInfo" 1
local domainid
local domainName
local domainInfo
local recordList
local recordInfo
local recordid
local recordName
local recordTtl
local recordType
local record_line_id
# use array because ipv4 and ipv6 may have same record name
local recordInfoList
unset changedRecords
unset nonExistentRecords
##firstIndex=`echo ${!domainList[@]} | awk '{print $1}'`
##lastIndex=`echo ${!domainList[@]} | awk '{print $NF}'`
local maxDomainListIndex=`echo ${!domainList[@]} | awk '{print $NF}'`
local domainGroup
local j k
local retryNum=2
local tryNum=0
for ((i=0;i<=$maxDomainListIndex;i++));do
domainGroup=${domainList[$i]}
j=0
for domain in ${domainGroup[@]};do
# the first item is main domain
if ((j==0));then
domainName=$domain
domainInfo=$(echo $domainListInfo|getJSONObjByKey 'name' $domainName)
if [ -z "$domainInfo" ];then
writeLog "DNSPOD tell me do not have domain $domainName,waiting..."
createDomain $domainName
domainListInfo=$(getDomainList)
tryNum=$((tryNum+1))
if [ $tryNum -lt $retryNum ]
then
i=$((i-1))
break 1
else
writeLog 'can not create domain or get domain info, exit dnspodsh.'
exit 1
fi
fi
domainid=$(getDataByKey "$domainInfo" 'id')
recordList=$(getRecordList $domainid)
if [ -z "$recordList" ];then
writeLog 'DNSPOD tell me record list null,waiting...'
fi
checkStatusCode "$recordList" 1
else
recordInfoList[0]=$(echo $recordList|getJSONObjBySubDomainRecordType $domain 'A')
recordInfoList[1]=$(echo $recordList|getJSONObjBySubDomainRecordType $domain 'AAAA')
case $defaultRecordType in
'A')
recordInfoList[1]=''
if [ -z "${recordInfoList[0]}" ];then
recordType='A'
nonExistentRecords[${#nonExistentRecords[@]}]="$domainid $domain $recordType $defaultRecordLineId $newipv4 $domainName"
continue
fi
;;
'AAAA')
recordInfoList[0]=''
if [ -z "${recordInfoList[1]}" ];then
recordType='AAAA'
nonExistentRecords[${#nonExistentRecords[@]}]="$domainid $domain $recordType $defaultRecordLineId $newipv6 $domainName"
continue
fi
;;
'AAAAA')
if [ -z "${recordInfoList[0]}" ];then
recordType='A'
nonExistentRecords[${#nonExistentRecords[@]}]="$domainid $domain $recordType $defaultRecordLineId $newipv4 $domainName"
fi
if [ -z "${recordInfoList[1]}" ];then
recordType='AAAA'
nonExistentRecords[${#nonExistentRecords[@]}]="$domainid $domain $recordType $defaultRecordLineId $newipv6 $domainName"
fi
[ -z "${recordInfoList}" ] && continue
;;
esac
for ((k=0;k<2;k++));do
recordInfo=${recordInfoList[$k]}
[ -z "$recordInfo" ] && continue
recordType=$(getDataByKey "$recordInfo" 'type')
oldip=$(getDataByKey "$recordInfo" 'value')
if ! checkip "$oldip";then
writeLog "get old ip error!it is $oldip.waiting..."
continue
fi
case $recordType in
'A') newip=$newipv4;;
'AAAA') newip=$newipv6;;
esac
if [ "$newip" != "$oldip" ];then
recordid=$(getDataByKey "$recordInfo" 'id')
recordName=$(getDataByKey "$recordInfo" 'name')
recordTtl=$(getDataByKey "$recordInfo" 'ttl')
record_line_id=$(getDataByKey "$recordInfo" 'line_id')
if [ -n "$recordid" ] && [ -n "$recordTtl" ] && [ -n "$recordType" ]; then
changedRecords[${#changedRecords[@]}]="$domainid $recordid $domain $recordType $record_line_id $recordTtl $newip $domainName"
fi
fi
done
fi
j=$((j+1))
done
done
}
setType()
{
case "$ENABLE_IPv4$ENABLE_IPv6" in
'10') defaultRecordType='A';;
'01') defaultRecordType='AAAA';;
'11')
# Note: No precedence between ipv4 and ipv6. All record_type A and AAAA with same sub_domain will be updated
defaultRecordType='AAAAA'
;;
'00')
writeLog 'IPv4 and IPv6 both failed. Please check your config file.'
writeLog 'exit dnspodsh'
exit 1
;;
esac
}
go()
{
local getipv4_retry=0 getipv6_retry=0
while :
do
if [ $ENABLE_IPv4 -eq 1 ] && getipv4 && ! checkip "$newipv4";then
writeLog 'get new ipv4 failed, waiting...'
sleep $delay
getipv4_retry=$((getipv4_retry+1))
if [ $getipv4_retry -eq $retry_count ];then
writeLog 'can not get new ipv4, ignore ipv4'
ENABLE_IPv4=0
else
continue
fi
fi
break
done
while :
do
if [ $ENABLE_IPv6 -eq 1 ] && getipv6 && ! checkip "$newipv6";then
writeLog 'get new ipv6 failed, waiting...'
sleep $delay
getipv6_retry=$((getipv6_retry+1))
if [ $getipv6_retry -eq $retry_count ];then
writeLog 'can not get new ipv6, ignore ipv6'
ENABLE_IPv6=0
else
continue
fi
fi
break
done
setType
getChangedRecords
local actionFlag=0
if (( ${#changedRecords[@]} > 0 ));then
actionFlag=1
writeLog "ip is changed. set records..."
setRecords
fi
if (( ${#nonExistentRecords[@]} > 0 ));then
actionFlag=1
writeLog 'Starting create non-existent sub domains.'
createRecords
writeLog 'createRecords done.'
fi
[ $actionFlag -eq 0 ] && writeLog 'No update. Do nothing.'
return 0
}
# INIT
# load config file
dnspodshDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
[ -f "$dnspodshDir/dnspod.conf" ] && config_file="$dnspodshDir/dnspod.conf" || config_file="$HOME/dnspod.conf"
[ ! -r $config_file ] && echo "ERROR: config file does not exist. no such file: \"$dnspodshDir/dnspod.conf\" or \"$HOME/dnspod.conf\"" && exit 1
. $config_file
if [ $# == 3 ] && [[ $1 == '--set' ]];then
save_conf $2 $3
eval $2=$3
fi
logDir=${logDir:-$dnspodshDir}
declare -i logMaxLines=${logMaxLines-100}
logFile=$logDir'/dnspod.log'
traceFile=$logDir'/dnspodshtrace.log'
mkdir -p $logDir
touch $logFile
IP=$(which ip)
CURL=$(which curl)
[ -z "$IP" ] && { writeLog 'ERROR: dnspodsh require ip, please install iproute2';exit 1;}
[ -z "$CURL" ] && { writeLog 'ERROR: dnspodsh require cURL, please install curl';exit 1;}
[ -z "$login_token" ] && { writeLog "ERROR: login token is empty, please check \"$config_file\"";exit 1;}
[ -z "$(echo ${domainList[@]})" ] && { writeLog "ERROR: all ${#domainList[@]} domainList are empty, please check \"$config_file\"";exit 1;}
ENABLE_IPv4=${ddns_ipv4-1}
ENABLE_IPv6=${ddns_ipv6-0}
declare -i retry_count=${retry_count-1}
delay=${delay-5}
defaultRecordLineId=${defaultRecordLineId:-'0'}
newip=
newipv4=
newipv6=
defaultRecordType=
ipv4Url=${ipv4Url:-'http://myip.ipip.net'}
ipv6Url=${ipv6Url:-'http://v6.ipv6-test.com/api/myip.php'}
apiUrl=${apiUrl:-'https://dnsapi.cn/'}
format="json"
lang="cn"
commonPost="login_token=$login_token&format=$format&lang=$lang&error_on_empty=no"
# RUN
go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment