利用华为云 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. 欧文斯

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

  2. 小白
    小白 回复

    Windows 10Chrome 124.0.0.0来自 四川 的大神

    我账号密码是对,还是提示我账号密码错误

    1. 欧文斯

      我自己是没问题的,你是不是开了两步验证了