利用华为云 API 实现自动 DDNS 功能|支持IPv4|IPv6

小助手读文章 00:00 / 00:00

温馨提示:
本文所述内容具有依赖性,可能因软硬条件不同而与预期有所差异,故请以实际为准,仅供参考。

DDNS 与常规 DNS 不同的地方就是,DDNS 是动态的,而 DNS 是静态的。所以所谓 DDNS 其实就是利用 DNS 服务商的 API 接口,近乎实时地去更新 DNS 解析而已。

网上其实也有很多现成的工具和服务商,比如 ddnsclient、no-ip、花生壳等等,那么为什么还要自己造一个呢?这是因为网上的其实或多或少有限制,比如 no-ip 每 30 天要点链接确认状态。

准备

  • 华为云账号、密码,且域名已经托管在华为云上;
  • 执行环境已安装 jq 命令,如果要更新远程 IP,则还要安装 sshpass

功能

  • 自动检测公网 IP;
  • 自动更新域名解析;
  • 解析不存在则自动创建;

脚本

#!/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."

效果

首次执行:

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.

后续更新:

Initiated.
DDNS domain: zd.vircloud.net
IPv4 detected: 1.2.3.4
IP has not changed.
Done.
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.

相关文章:

1、《利用 CloudFlare API 实现自动 DDNS 功能|支持IPv4|IPv6


ArmxMod for Typecho
个性化、自适应、功能强大的响应式主题

推广

 继续浏览关于 ipv6华为云脚本ddnsipv4自动化自动功能经验分享 的文章

 本文最后更新于 2023/08/16 13:39:28,可能因经年累月而与现状有所差异

 引用转载请注明: VirCloud's Blog > 运维 > 利用华为云 API 实现自动 DDNS 功能|支持IPv4|IPv6

精选评论

  1. timochan
    timochan 回复

    未知操作系统Chrome 114.0.0.0来自 亚太地区 的大神

    不过还是有缺陷,各个 DNS 不一定遵守你的 TTL 值,对于自己来说是实时更新,但是对于 DNS 解析结果可不一定(不过这也是难以避免的。

    1. 欧文斯

      这个就不是我们能控制的了