openwrt带的ddns不支持dnspod.然后弄了这个脚本,用于更新dnspod的动态域名解析.

如果想要纯IPV6解析,或者IPV4+IPV6双解析.必须要AAAA解析.

 

弄了一个脚本,主要是IPV6,当然脚本也支持IPV4.

在贴脚本之前,先提供一个临时方案.

 

中转方案

 

dynv6.net这家运营商在ddns支持列表.

且同时支持IPV6+IPV4双解析.

 

演示域名

windows下

ping -4 t2.getce.cn

ping -6 t2.getce.cn 

 

非windows

ping t2.getce.cn

ping6 t2.getce.cn

 

添加dynv6.net解析

 

先用dynv6.net添加IPV4 和IPV6双解析的ddns设置.

这个luci界面可以简单处理.

 

 

cname解析到dynv6.net

 

在dnspod中添加cname解析到dynv6.net即可.

 

 

优点

解析方便.设置简单.一般场景足够了.

 

缺点

cname解析和mx解析冲突.

用以下解析才不会.

  • A+MX  (IPV4解析 +邮箱)
  • AAAA+MX (IPV6解析 +邮箱)
  • A+AAAA+MX (IPV4 + IPV6 +邮箱)

 

 

 

DNSPOD直解方案

 

也就是直接路由器脚本更新DNSPOD的AAAA和A记录.

 

我这里演示主要是AAAA解析.A记录只需要将脚本复制一份,稍微修改下即可.

 

 

 

 

新建一个AAAA解析

 

AAAA解析就是IPV6的解析.

这步不说明,我用的是DNSPOD,也就现在的腾讯云

以下域名部分的说明都是dnspod.

 

 

路由器添加解析脚本

 

要求

首先要保证你wget 支持https,

如果wget不支持https,安装一个"ca-"开头的软件包,有两个. 路由容量够就都装.

 

 

解析脚本

这个脚本我稍微修改过,适应路由器wget.

脚本上有原作者信息.

 

 

将文件保存到 /root/dnspod.sh

#!/bin/bash

#################################################
# AnripDdns v5.08
# Dynamic DNS using DNSPod API
# Original by anrip<mail@anrip.com>, http://www.anrip.com/ddnspod
# Edited by ProfFan
#################################################

#################################################
# 2018-11-06 
# support  LAN / WAN / IPV6 resolution

# 2019-05-24
# Support Ipv6 truly (Yes, it was just claimed to, but actually not = =!)
# Add another way resolving IPv6, for machines without nvram.

#if you have any issues, please let me know.
# https://blog.csdn.net/Imkiimki/article/details/83794355
# Daleshen mailto:gf@gfshen.cn

#################################################

#Please select IP type
IPtype=3  #1.WAN 2.LAN 3.IPv6
#---------------------
if [ $IPtype = '3' ]; then
    record_type='AAAA'
else
    record_type='A'
fi
echo Type: ${record_type}

# OS Detection
case $(uname) in
  'Linux')
    echo "OS: Linux"
    arIpAddress() {

	case $IPtype in
		'1')
				
		curltest=`which curl`
		if [ -z "$curltest" ] || [ ! -s "`which curl`" ] 
		then
			#根据实际情况选择使用合适的网址
			#wget --no-check-certificate --quiet --output-document=- "https://www.ipip.net" | grep "IP地址" | grep -E -o '([0-9]+\.){3}[0-9]+' | head -n1 | cut -d' ' -f1
			wget --no-check-certificate --secure-protocol=TLSv1_2 --quiet --output-document=- "http://members.3322.org/dyndns/getip" | grep -E -o '([0-9]+\.){3}[0-9]+' | head -n1 | cut -d' ' -f1
			#wget --no-check-certificate --secure-protocol=TLSv1_2 --quiet --output-document=- "ip.6655.com/ip.aspx" | grep -E -o '([0-9]+\.){3}[0-9]+' | head -n1 | cut -d' ' -f1
			#wget --no-check-certificate --secure-protocol=TLSv1_2 --quiet --output-document=- "ip.3322.net" | grep -E -o '([0-9]+\.){3}[0-9]+' | head -n1 | cut -d' ' -f1
		else
		curl -k -s "http://members.3322.org/dyndns/getip" | grep -E -o '([0-9]+\.){3}[0-9]+' | head -n1 | cut -d' ' -f1
		#curl -L -k -s "https://www.ipip.net" | grep "IP地址" | grep -E -o '([0-9]+\.){3}[0-9]+' | head -n1 | cut -d' ' -f1

		#curl -k -s ip.6655.com/ip.aspx | grep -E -o '([0-9]+\.){3}[0-9]+' | head -n1 | cut -d' ' -f1
		#curl -k -s ip.3322.net | grep -E -o '([0-9]+\.){3}[0-9]+' | head -n1 | cut -d' ' -f1		
		fi
		;;
 
		'2')
		
		ip -o -4 addr list | grep -Ev '\s(docker|lo)' | awk '{print $4}' | cut -d/ -f1 
		;;
 
		'3')
		
		# 因为一般ipv6没有nat ipv6的获得可以本机获得
		#ifconfig $(nvram get wan0_ifname_t) | awk '/Global/{print $3}' | awk -F/ '{print $1}' 
		ip addr show dev br-lan |grep "global dynamic" | sed -e's/^.*inet6 \([^ ]*\)\/.*$/\1/;t;d;'| awk 'NR==1' #如果没有nvram,使用这条,注意将eth0改为本机上的网口设备 (通过 ifconfig 查看网络接口)
		;;
 	esac
 
    }
    ;;
  'FreeBSD')
    echo 'FreeBSD'
    exit 100
    ;;
  'WindowsNT')
    echo "Windows"
    exit 100
    ;;
  'Darwin')
    echo "Mac"
    arIpAddress() {
        ifconfig | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}'
    }
    ;;
  'SunOS')
    echo 'Solaris'
    exit 100
    ;;
  'AIX')
    echo 'AIX'
    exit 100
    ;;
  *) ;;
esac

echo "Address: $(arIpAddress)"

# Get script dir
# See: http://stackoverflow.com/a/29835459/4449544
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.

  target=$1 fname= targetDir= CDPATH=

  { \unalias command; \unset -f command; } >/dev/null 2>&1
  [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.

  while :; do # Resolve potential symlinks until the ultimate target is found.
      [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
      command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
      fname=$(command basename -- "$target") # Extract filename.
      [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
      if [ -L "$fname" ]; then
        # Extract [next] target path, which may be defined
        # *relative* to the symlink's own directory.
        # Note: We parse `ls -l` output to find the symlink target
        #       which is the only POSIX-compliant, albeit somewhat fragile, way.
        target=$(command ls -l "$fname")
        target=${target#* -> }
        continue # Resolve [next] symlink target.
      fi
      break # Ultimate target reached.
  done
  targetDir=$(command pwd -P) # Get canonical dir. path
  # Output the ultimate target's canonical path.
  # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
  if [ "$fname" = '.' ]; then
    command printf '%s\n' "${targetDir%/}"
  elif  [ "$fname" = '..' ]; then
    # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
    # AFTER canonicalization.
    command printf '%s\n' "$(command dirname -- "${targetDir}")"
  else
    command printf '%s\n' "${targetDir%/}/$fname"
  fi
)

DIR=$(dirname -- "$(readlink "$0")")

# Global Variables:

# Token-based Authentication
arToken=""
# Account-based Authentication
arMail=""
arPass=""

# Load config

#. $DIR/dns.conf

# Get Domain IP
# arg: domain
arDdnsInfo() {
    local domainID recordID recordIP
    # Get domain ID
    domainID=$(arApiPost "Domain.Info" "domain=${1}")
    
    domainID=$(echo $domainID | sed 's/.*{"id":"\([0-9]*\)".*/\1/')
    
    # Get Record ID
    recordID=$(arApiPost "Record.List" "domain_id=${domainID}&sub_domain=${2}&record_type=${record_type}")
    
    recordID=$(echo $recordID | sed 's/.*\[{"id":"\([0-9]*\)".*/\1/')
    
    # Last IP
    recordIP=$(arApiPost "Record.Info" "domain_id=${domainID}&record_id=${recordID}&record_type=${record_type}")
    
    recordIP=$(echo $recordIP | sed 's/.*,"value":"\([0-9a-z\.:]*\)".*/\1/')
    
    # Output IP
    case "$recordIP" in 
      [1-9a-z]*)
        echo $recordIP
        return 0
        ;;
      *)
        echo "Get Record Info Failed!"
        return 1
        ;;
    esac
}

# Get data
# arg: type data
# see Api doc: https://www.dnspod.cn/docs/records.html#
arApiPost() {
    local agent="AnripDdns/5.07(mail@anrip.com)"
    #local inter="https://dnsapi.cn/${1:?'Info.Version'}"
    local inter="https://dnsapi.cn/${1}"
    if [ "x${arToken}" = "x" ]; then # undefine token
        local param="login_email=${arMail}&login_password=${arPass}&format=json&${2}"
    else
        local param="login_token=${arToken}&format=json&${2}"
    fi
    wget --quiet --no-check-certificate  -O - --post-data=$param $inter
}

# Update
# arg: main domain  sub domain
arDdnsUpdate() {
    local domainID recordID recordRS recordCD recordIP myIP
    
  
    # Get domain ID
    domainID=$(arApiPost "Domain.Info" "domain=${1}")
    domainID=$(echo $domainID | sed 's/.*{"id":"\([0-9]*\)".*/\1/')
    #echo $domainID
    # Get Record ID
    recordID=$(arApiPost "Record.List" "domain_id=${domainID}&record_type=${record_type}&sub_domain=${2}")
    recordID=$(echo $recordID | sed 's/.*\[{"id":"\([0-9]*\)".*/\1/')
    #echo $recordID
    # Update IP
    myIP=$(arIpAddress)
    recordRS=$(arApiPost "Record.Modify" "domain_id=${domainID}&sub_domain=${2}&record_type=${record_type}&record_id=${recordID}&record_line=默认&value=${myIP}")
    recordCD=$(echo $recordRS | sed 's/.*{"code":"\([0-9]*\)".*/\1/')
    recordIP=$(echo $recordRS | sed 's/.*,"value":"\([0-9a-z\.:]*\)".*/\1/')
    
    # Output IP
    if [ "$recordIP" = "$myIP" ]; then
        if [ "$recordCD" = "1" ]; then
            echo $recordIP
            return 0
        fi
        # Echo error message
        echo $recordRS | sed 's/.*,"message":"\([^"]*\)".*/\1/'
        return 1
    else
        echo $recordIP #"Update Failed! Please check your network."
        return 1
    fi
}

# DDNS Check
# Arg: Main Sub
arDdnsCheck() {
    local postRS
    local lastIP
    local hostIP=$(arIpAddress)
    echo "Updating Domain: ${2}.${1}"
    echo "hostIP: ${hostIP}"
    lastIP=$(arDdnsInfo $1 $2)
    if [ $? -eq 0 ]; then
        echo "lastIP: ${lastIP}"
        if [ "$lastIP" != "$hostIP" ]; then
            postRS=$(arDdnsUpdate $1 $2)
             
            if [ $? -eq 0 ]; then
                echo "update to ${postRS} successed."
                return 0
            else
                echo ${postRS}
                return 1
            fi
        fi
        echo "Last IP is the same as current, no action."
        return 1
    fi
    echo ${lastIP}
    return 1
}

# DDNS
#echo ${#domains[@]}
#for index in ${!domains[@]}; do
#    echo "${domains[index]} ${subdomains[index]}"
#    arDdnsCheck "${domains[index]}" "${subdomains[index]}"
#done

. $DIR/dns.conf

 

 

 

配置文件

保存到脚本同目录下,文件名是上面脚本指定的.

所以保存到:

/root/dns.conf

 

# 指定你DNSPODtoken
arToken="10xxxx,dbxxxxxxxxxxxxxxxxxxxxxxxxxx"

# 2. 添加你要ddns解析的域名,一行一个
# 下面记录就是更新 ipv6.getce.cn
#  yge.me 是你域名
#  ipv6 是子域名
# 解析是固定AAAA解析,前面脚本固定了. 

arDdnsCheck "yge.me" "ipv6"

 

 

测试脚本

 

root@OpenWrt:~# sh  /root/dnspod.sh


# 输出
Type: AAAA
OS: Linux
Address: 240e:370:3f0c:ce0::1
Updating Domain: ipv6.getce.cn
hostIP: 240e:370:3f0c:ce0::1
lastIP: 240e:370:3f08:cba0::1
update to 240e:370:3f0c:ce0::1 successed.

 # 如果IP没有变化会提示no action.
Type: AAAA
OS: Linux
Address: 240e:370:3f0c:ce0::1
Updating Domain: ipv6.getce.cn
hostIP: 240e:370:3f0c:ce0::1
lastIP: 240e:370:3f0c:ce0::1
Last IP is the same as current, no action.

 

看到update成功就是对的了.如果看到wget错误,极有可能就是ssl支持问题. 参考前面说明安装 "ca-"开头的软件包.

 

 

::1 结尾的一般就是路由器ip.

 

添加cron

*/5 * * * * /bin/sh /root/dnspod.sh  >>/tmp/dnspod.log 2>&1

# tmp生成一个日志文件.自己可以查看

每5分钟执行一次.

如果要求高一些可以设置短一些.

 

脚本做了校验,只有IP变动才会上传新IP到DNSPOD.

所以时间间隔短一些也没问题.

 

 

A解析

 

如果需要A解析,只需要将前面两个文件复制一份,稍微修改.

其他照葫芦画瓢即可.

 

主要修改解析脚本这两行.

 

IPtype=3  #1.WAN 2.LAN 3.IPv6

. $DIR/dns.conf

 

IPtyep = 1

配置文件另外指定一个,比如:

dns_v4.conf

 

改完自己测试下,没问题再添加到cron中执行即可.

 

 

热拔插事件

 

LINUX系统有个热拔插事件,无论是USB或者网络断开连接都会自动触发.

 

不作过多介绍.想了解更多 -- > https://github.com/wywincl/hotplug

 

 

vi /etc/hotplug.d/iface/98-dnspod-up

 

新建并保存

 

#!/bin/bash
[ "$ACTION" = ifup ] || exit 0
[ "$INTERFACE" = vwan1 ] || exit 0
/bin/sh /root/dnspod.sh  >>/tmp/dnspod_up.log 2>&1

 

我这里是多播,所以我的是vwan1 一般如果不是多播的用户这里是wan

 

参考

https://isnimitz.com/posts/ddns-with-openwrt-and-dnspod/

https://my.oschina.net/kmwzjs/blog/687408