在 OpenWrt 环境下使用 DnsPod 来实现动态域名解析

原来一直在使用 3322.org 的动态域名解析,而且之前还写过一篇如何在 OpenWrt 上使用 3322.org 的动态域名的文章。但是一段时间不用后,发现 3322.org 现在的动态域名只支持一个免费域名,而且怎么设置都无法正常工作。上网转悠了一下后,发现 DnsPod 有提供 客户端 API,其中的 DNSPod用户API文档 有详细的描述如何使用 DDNS 功能。于是乎就自己写了一个基于 DnsPod 客户端 API 的 Shell 脚本。

要使用这个脚本你需要具备以下条件:

  • 一个顶级域名并且在 DnsPod 进行域名解析
  • 一个运行 OpenWrt 的路由器
  • 会使用 SSH 客户端登陆到路由器
  • 会传输文件到路由器
  • 基本的 Linux 命令行操作

首先登陆到你的路由器,安装 wget,因为 OpenWrt 默认自带的基于 busybox 的 wget 无法处理 HTTPS 请求,而 DnsPod 的 API 处于安全考虑,强制使用 HTTPS 连接。下面的例子中
192.168.1.1 是路由器的 IP 地址,请根据实际情况调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
$ ssh root@192.168.1.1


BusyBox v1.22.1 (2014-09-20 22:31:09 CEST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

_______ ________ __
| |.-----.-----.-----.| | | |.----.| |_
| - || _ | -__| || | | || _|| _|
|_______|| __|_____|__|__||________||__| |____|
|__| W I R E L E S S F R E E D O M
-----------------------------------------------------
BARRIER BREAKER (14.07, r42625)
-----------------------------------------------------
* 1/2 oz Galliano Pour all ingredients into
* 4 oz cold Coffee an irish coffee mug filled
* 1 1/2 oz Dark Rum with crushed ice. Stir.
* 2 tsp. Creme de Cacao
-----------------------------------------------------
# opkg update
Downloading http://downloads.openwrt.org/barrier_breaker/14.07/ar71xx/nand/packages/base/Packages.gz.
Updated list of available packages in /var/opkg-lists/barrier_breaker_base.
Downloading http://downloads.openwrt.org/barrier_breaker/14.07/ar71xx/nand/packages/luci/Packages.gz.
Updated list of available packages in /var/opkg-lists/barrier_breaker_luci.
Downloading http://downloads.openwrt.org/barrier_breaker/14.07/ar71xx/nand/packages/packages/Packages.gz.
Updated list of available packages in /var/opkg-lists/barrier_breaker_packages.
Downloading http://downloads.openwrt.org/barrier_breaker/14.07/ar71xx/nand/packages/routing/Packages.gz.
Updated list of available packages in /var/opkg-lists/barrier_breaker_routing.
Downloading http://downloads.openwrt.org/barrier_breaker/14.07/ar71xx/nand/packages/telephony/Packages.gz.
Updated list of available packages in /var/opkg-lists/barrier_breaker_telephony.
Downloading http://downloads.openwrt.org/barrier_breaker/14.07/ar71xx/nand/packages/management/Packages.gz.
Updated list of available packages in /var/opkg-lists/barrier_breaker_management.
Downloading http://downloads.openwrt.org/barrier_breaker/14.07/ar71xx/nand/packages/oldpackages/Packages.gz.
Updated list of available packages in /var/opkg-lists/barrier_breaker_oldpackages.
Downloading http://openwrt-dist.sourceforge.net/releases/ar71xx/packages/Packages.gz.
Updated list of available packages in /var/opkg-lists/openwrt_dist.
Downloading http://openwrt-dist.sourceforge.net/releases/luci/packages/Packages.gz.
Updated list of available packages in /var/opkg-lists/openwrt_dist_luci.
# opkg install wget
Installing wget (1.16-1) to root...
Downloading http://downloads.openwrt.org/barrier_breaker/14.07/ar71xx/nand/packages/packages/wget_1.16-1_ar71xx.ipk.
Configuring wget.

接着来获得我已经写好的 DDNS 脚本,你可以在 GitHub Gist 上找到最新的版本:https://gist.github.com/TommyLau/21089ac976ef5fdfc39c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# wget --no-check-certificate "https://gist.github.com/TommyLau/21089ac976ef5fdfc39c/raw/51a45400e54291ab9bbc982b087f80dc57db5982/ddns.sh"
--2015-02-25 10:05:07-- https://gist.github.com/TommyLau/21089ac976ef5fdfc39c/raw/51a45400e54291ab9bbc982b087f80dc57db5982/ddns.sh
Resolving gist.github.com... 192.30.252.141
Connecting to gist.github.com|192.30.252.141|:443... connected.
WARNING: cannot verify gist.github.com's certificate, issued by '/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA':
Unable to locally verify the issuer's authority.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://gist.githubusercontent.com/TommyLau/21089ac976ef5fdfc39c/raw/51a45400e54291ab9bbc982b087f80dc57db5982/ddns.sh [following]
--2015-02-25 10:05:23-- https://gist.githubusercontent.com/TommyLau/21089ac976ef5fdfc39c/raw/51a45400e54291ab9bbc982b087f80dc57db5982/ddns.sh
Resolving gist.githubusercontent.com... 103.245.222.133
Connecting to gist.githubusercontent.com|103.245.222.133|:443... connected.
WARNING: cannot verify gist.githubusercontent.com's certificate, issued by '/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance CA-3':
Unable to locally verify the issuer's authority.
HTTP request sent, awaiting response... 200 OK
Length: 2842 (2.8K) [text/plain]
Saving to: 'ddns.sh'

ddns.sh 100%[================================================================================================================>] 2.78K --.-KB/s in 0.09s

2015-02-25 10:05:25 (31.0 KB/s) - 'ddns.sh' saved [2842/2842]

把新下载回来的文件设置为可执行

1
# chmod +x ddns.sh

修改 crontab

1
# crontab -e

增加如下的内容

1
*/5 * * * * /root/ddns.sh your@email.com YourPassword test.com www > /dev/null

上述的命令表示每 5 分钟检查一次域名更新,如果发现 IP 有变化的话,就自动更新相应的域名(www.test.com)。

  • your@email.com,这个请替换成你的 DnsPod 账号
  • YourPassword,请替换成你自己的密码
  • test.com,这个替换成你在 DnsPod 解析的域名
  • www,这个是要解析的子域名

PS: 如果你无法访问 GitHub Gist 的话,下面是脚本的内容,你需要自行复制到路由器里。

ddns.shlink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#!/bin/sh

########################################
#
# Tommy DnsPod DDNS Client v0.1.0
#
# Author: Tommy Lau <tommy@gen-new.com>
#
# Created: 2015-02-23 08:52:00 UTC
# Updated: 2015-02-23 12:15:46 UTC
#
########################################

# Use 'json', other option is 'xml'
format='json'

# Use English for default, 'cn' for Chinese
language='en'

# API URL
api_url='https://dnsapi.cn/'

# Get current IP
get_ip() {
local inter="http://members.3322.org/dyndns/getip"
wget --quiet --no-check-certificate --output-document=- $inter
#curl --silent $inter
}

# Send the API request to DnsPod API
# @param1: The command to execute, for example, Info.Version and etc.
# @param2: The parameters to send to the API, for example, domain='domain.tld'
api_post() {
# Client agent
local agent="Tommy DnsPod Client/0.1.0 (tommy@gen-new.com)"

# Stop if no API command is given
local inter="$api_url${1:?'Info.Version'}"

# Default post content for every request
local param="login_email=$email&login_password=$password&format=$format&lang=$language&${2}"

wget --quiet --no-check-certificate --output-document=- --post-data "$param" --user-agent="$agent" $inter
#curl --silent --request POST --data "$param" --user-agent "$agent" $inter
}

# Lookup current ip
# @param1: The domain to nslookup
dns_lookup() {
local server="114.114.114.114"
nslookup ${1} $server | tr -d '\n[:blank:]' | sed 's/.\+1 \([0-9\.]\+\).*/\1/'
}

# Update the DNS record
# @param1: The domain name to update, for example, 'domain.tld'
# @param2: The subdomain, for example, 'www'
dns_update() {
local current_ip=$(get_ip)
local dns_ip=$(dns_lookup "${2}.${1}")

echo "${current_ip} : ${dns_ip}"

if [ "$current_ip" == "$dns_ip" ]; then
echo "No need to update DDNS."
return 0
fi

# Get domain id
local domain_id=$(api_post "Domain.Info" "domain=${1}")
domain_id=$(echo $domain_id | sed 's/.\+{"id":"\([0-9]\+\)".\+/\1/')

# Get record id of the subdomain
local record_id=$(api_post "Record.List" "domain_id=${domain_id}&sub_domain=${2}")
record_id=$(echo $record_id | sed 's/.\+\[{"id":"\([0-9]\+\)".\+/\1/')

# Update the record
local result=$(api_post "Record.Ddns" "domain_id=${domain_id}&record_id=${record_id}&record_line=默认&sub_domain=${2}")
result_code=$(echo $result | sed 's/.\+{"code":"\([0-9]\+\)".\+/\1/')
result_message=$(echo $result | sed 's/.\+,"message":"\([^"]\+\)".\+/\1/')

# Output
echo "Code: $result_code, Message: $result_message"
}

# Email for login
email="${1:?'Please input e-mail'}"

# Password for the account
password=${2:?'Please input your password'}

# Domain
domain=${3:?'Please input domain'}

# Sub domain
subdomain=${4:?'Please input sub domain'}

# Update the DDNS
dns_update "$domain" "$subdomain"