DDNS 与常规 DNS 不同的地方就是,DDNS 是动态的,而 DNS 是静态的。所以所谓 DDNS 其实就是利用 DNS 服务商的 API 接口,近乎实时地去更新 DNS 解析而已。
网上其实也有很多现成的工具和服务商,比如 ddnsclient、no-ip、花生壳等等,那么为什么还要自己造一个呢?这是因为网上的其实或多或少有限制,比如 no-ip 每 30 天要点链接确认状态。
准备 {#article-header-0}
- 华为云账号、密码,且域名已经托管在华为云上;
- 执行环境已安装
jq
命令,如果要更新远程 IP,则还要安装sshpass
;
功能 {#article-header-1}
- 自动检测公网 IP;
- 自动更新域名解析;
- 解析不存在则自动创建;
脚本 {#article-header-2}
#!/bin/bash
############### 授权信息(需修改成你自己的) ################
用户名
username="登陆的用户名"
密码
password="登陆密码"
做 DDNS 的根域名
zone_name="根域名"
做 DDNS 的域名,创建成功后就是通过该域名访问内网资源
record_name="解析域名" #参考 ACME,选用如下地址 iam="iam.myhuaweicloud.com" dns="dns.ap-southeast-1.myhuaweicloud.com"
###################### 修改配置信息 #######################
域名类型,IPv4 为 A,IPv6 则是 AAAA
record_type="A"
IPv6 检测服务,本站检测服务仅在大陆提供
#ip=$(curl -s https://ipv6.vircloud.net)
IPv4 检测服务
ip=$(curl -s ifconfig.me) #如果是要检测远程 IP,可参考下条命令 #ip=$(sshpass -p '远程主机密码' ssh 远程主机账号@远程主机 IP 或域名 'curl -s ifconfig.me' 2>/dev/null)
文件保存地址
#current_dir=$(cd
dirname $0
; pwd) current_dir="/var/log/ddns-hw"域名后不可带点
record_name=${record_name%.}
变动前的公网 IP 保存位置
ip_file="${current_dir}/ip.${record_name}.txt"
授权保存位置
auth_file="${current_dir}/huaweidns.${record_name}.auth"
域名识别信息保存位置
id_file="${current_dir}/huaweidns.${record_name}.ids"
监测日志保存位置
log_file="${current_dir}/huaweidns.${record_name}.log"
################### 判断日志文件夹是否存在 ################## if [ ! -d "${current_dir}" ]; then mkdir -p ${current_dir} fi
###################### 监测日志格式 ######################## log() { if [ "${1}" ]; then echo -e "[$(date)] - ${1}" >> $log_file echo -e "${1}" fi } log "Initiated." log "DDNS domain: ${record_name}"
###################### 检查响应数据 ######################## check(){ if [ "${1}" ]; then log "${2}" if [[ ${1} == ""code"" ]]; then result=
echo ${1} | grep -Po '(?<="message":")[^"]*'
message=" ERROR ENCOUNTERED. RETURN MESSGAE: ${result}" log "${message}" if [ ! -n "${3}" ]; then exit 1 fi elif [[ ${1} == ""error_msg"" ]]; then result=echo ${1} | grep -Po '(?<="error_msg":")[^"]*'
message=" ERROR ENCOUNTERED. RETURN MESSGAE: ${result}" log "${message}" if [ ! -n "${3}" ]; then exit 1 fi elif [[ ${1} == ""total_count":0" ]]; then message=" ERROR ENCOUNTERED. RETURN MESSGAE: Doesn't exist." log "${message}" if [ ! -n "${3}" ]; then exit 1 fi else log " OK." fi else log "${2}" message=" NO DATA RECEIVED. CHECK NETWORK CONNECTIVITY." log "${message}" if [ ! -n "${3}" ]; then exit 1 fi fi }###################### 简单判断是否是 IP #################### if [ "${ip}" != "${1#[0-9].[0-9]}" ]; then log "IPv4 detected: ${ip}" elif [ "${ip}" != "${1#:[0-9a-fA-F]}" ]; then log "IPv6 detected: ${ip}" else log "No valid IP." log "Check Done" exit 0 fi
###################### 判断 IP 是否变化 #################### if [ -f ${ip_file} ]; then old_ip=$(cat ${ip_file}) else dns_ip=$(curl -s -k -H "accept: application/dns-json" "https://doh.360.cn/resolve?name=${record_name}&type=A") if [ -n "${dns_ip}" ]; then dns_status=$(echo ${dns_ip} | jq -r ".Status") if [ "${dns_status}" == "0" ]; then dns_ip=$(echo ${dns_ip} | jq -r ".Answer[] | select(.type == 1) | .data") else dns_ip="" fi if [ "${dns_ip}" ]; then old_ip=$(echo ${dns_ip} | head -n 1) else ping_dns=$(ping ${record_name} -c 1 2>/dev/null) ping_ip=$(echo ${ping_dns} | awk -F ' ' '{print $3}' | head -n 1) if [ ! -n "${ping_ip}" ]; then old_ip="" else old_ip=${ping_ip:1:${#ping_ip}-2} fi fi fi if [ ! -n "${old_ip}" ]; then log "No records detected." else echo "${old_ip}" > ${ip_file} log "Last recorded IP: ${old_ip}" fi fi if [ "${ip}" == "${old_ip}" ]; then log "IP has not changed." log "Done." exit 0 else log "IP changed, new IP: ${ip}, updating ..." fi
###################### 获取操作授权 ###################### get_token(){ auth_content=$(curl -L -k -s -D - -X POST "https://${iam}/v3/auth/tokens" -H "Content-Type: application/json;charset=utf8" --data-raw "{"auth": {"identity": {"methods": ["password"],"password": {"user": {"name": "${username}","password": "${password}","domain": {"name": "${username}"}}}},"scope": {"project": {"name": "ap-southeast-1"}}}}") check "${auth_content}" "Get token ..." auth_identifier=$(echo "${auth_content}" | grep X-Subject-Token | awk -F ' ' '{print $2}') auth_date=$(echo "${auth_content}" | grep "Date: " | awk -F ' ' '{print $2" "$4" "$3" "$5" "$6}') auth_expire=$(date -d "${auth_date} +1day" +%s) echo "${auth_expire} ${auth_identifier}" > ${auth_file} log "Token obtained successfully." } log "Check token ..." if [ -f ${auth_file} ] ; then auth_identifier=$(head -1 ${auth_file} | awk -F ' ' '{print $2}') auth_expire=$(head -1 ${auth_file} | awk -F ' ' '{print $1}') if [ ! -n "${auth_identifier}" ]; then log "Token doesn't exist, getting ..." get_token else current_date=$(date +%s) if [[ "${current_date}" > "${auth_expire}" ]]; then log "Token expires, refreshing ..." get_token fi fi log "Check token done." else log "Token doesn't exist, getting ..." get_token fi
###################### 获取域名 ID 信息 ###################### get_zone(){ zone_identifier=$(curl -s -X GET "https://${dns}/v2/zones?name=${zone_name}" -H "X-Auth-Token: ${auth_identifier}" -H "Content-Type: application/json") check "${zone_identifier}" "get zone_id ..." zone_identifier=$(echo "${zone_identifier}" | grep -Po '(?<="id":")[^"]' | head -1) } get_record(){ record_identifier=$(curl -s -X GET "https://${dns}/v2/zones/${zone_identifier}/recordsets?name=${record_name}&type=${record_type}" -H "X-Auth-Token: ${auth_identifier}" -H "Content-Type: application/json") check "${record_identifier}" "get record_id ..." ${1} record_identifier=$(echo "${record_identifier}" | grep -Po '(?<="id":")[^"]') } create_record(){ description=$(echo "Created on: "$(date +"%Y/%m/%d %H:%M:%S")) record_identifier=$(curl -s -X POST "https://${dns}/v2/zones/${zone_identifier}/recordsets" -H "X-Auth-Token: ${auth_identifier}" -H "Content-Type: application/json" --data-raw "{"name":"${record_name}.","description":"${description}","type":"${record_type}","ttl":120,"records":["${ip}"]}") check "${record_identifier}" "create record ..." record_identifier=$(echo "${record_identifier}" | grep -Po '(?<="id":")[^"]*') } log "Check ids ..." if [ -f ${id_file} ] ; then zone_identifier=$(head -1 ${id_file}) if [ ! -n "${zone_identifier}" ]; then log "Zone_id doesn't exist, getting ..." get_zone sed -i '1d' ${id_file} sed -i '1i ${zone_identifier}' ${id_file} log "Zond_id obtained successfully." fi record_identifier=$(tail -1 ${id_file}) if [ ! -n "${record_identifier}" ]; then log "Record_id doesn't exist, getting ..." get_record sed -i '2d' ${id_file} echo "${record_identifier}" >> ${id_file} log "Record_id obtained successfully." fi log "Check ids done." else log "Ids doesn't exist, getting ..." get_zone get_record "create" if [ ! -n "${record_identifier}" ]; then log "Record_id doesn't exist, creating ..." create_record log "Record_id created successfully." fi echo "${zone_identifier}" > ${id_file} echo "${record_identifier}" >> ${id_file} fi
###################### 更新 DNS 记录 ###################### description=$(echo "Last updated on: "$(date +"%Y/%m/%d %H:%M:%S")) update=$(curl -s -X PUT "https://${dns}/v2/zones/${zone_identifier}/recordsets/${record_identifier}" -H "X-Auth-Token: ${auth_identifier}" -H "Content-Type: application/json" --data-raw "{"name":"${record_name}.","description" : "${description}","type":"${record_type}","ttl":120,"records":["${ip}"]}"")
check "${update}" "Updating record ..." "create" record_identifier=$(echo "${update}" | grep -Po '(?<="id":")[^"]*')
if [ ! -n "${record_identifier}" ]; then log "Record_id doesn't exist, creating ..." create_record log "Record_id created successfully." sed -i '2d' ${id_file} echo "${record_identifier}" >> ${id_file} log "Record_id updated successfully." fi
message="IP has been updated to: ${ip}" echo "${ip}" > ${ip_file} log "${message}"
log "Done."
<span aria-hidden="true" class="line-numbers-rows"> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> <span> </span> </span> </code> </pre>
Sh
Copy
效果 {#article-header-3}
首次执行:
Initiated. DDNS domain: zd.vircloud.net IPv4 detected: 1.2.3.4 No records detected. IP changed, new IP: 1.2.3.4, updating ... Check token ... Token doesn't exist, getting ... Get token ... OK. Token obtained successfully. Check ids ... Ids doesn't exist, getting ... get zone_id ... OK. get record_id ... ERROR ENCOUNTERED. RETURN MESSGAE: Doesn't exist. Record_id doesn't exist, creating ... create record ... OK. Record_id created successfully. Updating record ... OK. IP has been updated to: 1.2.3.4 Done.
Sh
Copy
后续更新:
Initiated. DDNS domain: zd.vircloud.net IPv4 detected: 1.2.3.4 IP has not changed. Done.
Sh
Copy
Initiated. DDNS domain: zd.vircloud.net IPv4 detected: 1.2.3.5 IP changed, new IP: 1.2.3.5, updating ... Check token ... Check token done. Check ids ... Check ids done. Updating record ... OK. IP has been updated to: 1.2.3.5 Done.
Sh
Copy
相关文章:
1、《 利用 CloudFlare API 实现自动 DDNS 功能|支持IPv4|IPv6 》