Chúng ta có thể xây dựng một script để DDNS service của DSM tự động cập nhật IP cho DNS Cloudflare. Cách làm này có những cái tiện/bất tiện như sau:
- DSM tự lấy WAN IP cung cấp cho script
- DSM đã cài sẵn trình phân tích json jq
- DSM tự kiểm tra xem IP có thay đổi hay không để gọi script cập nhật
- DSM không cho cài đặt nhiều DDNS từ một Service Provider
Script của chúng ta cập nhật tất cả domain/subdomain của một account Cloudflare, trừ những domain/subdomain được chỉ định rõ qua regular expression. Chúng ta chỉ định qua tham số hostname như sau:
# Các domain sau đây có IP tĩnh, không update
# Đặt cách nhau chuỗi ---
hostname: example.org---sub.domain.com
Trường hợp có những ký tự không thể nhập ở input hostname thì nhập trực tiếp ở file /etc/ddns.conf
[USER_Cloudflare_All]
interface_v6=default
hostname=example.org---^sub.domain.com$
passwd=XZfkh1234567890l4OKi8s+abcdef7i0hTxisDlTHA==
net=DEFAULT
status=
ip=114.16.150.39
service=true
username=email@gmail.com
enable_heartbeat=no
provider=USER_Cloudflare_All
ipv6=0:0:0:0:0:0:0:0
interface_v4=default
Script cfip_all.sh
Bash script có sử dụng curl và jq
#!/bin/bash
msg=$(cat <<EOT
cfip_all.sh
© 2020 LNT <lnt@ly-le.info>
version 20231001
Cập nhật IP cho tất cả record A thuộc một tài khoản cloudflare.com
---
EOT
)
debug=0 ## 0: không ghi log
E="✘ Bỏ qua!"
log=${0%/*}/$(basename $0).log
[ $# -ne 4 ] && { (($debug))&&echo -e "$msg\nLỗi sai tham số">>$log||echo 'Insufficient parameters';exit 1; }
AUTH_EMAIL="$1"
AUTH_KEY="$2"
re="`sed 's/---/|/g'<<<$3`"
[ -z "$re" ]||re="($re)"
ip="$4"
SHM="/dev/shm/cfip_all.sh"
[ -e "$SHM" ]&&oIp=$(<$SHM)||oIp=''
printf $ip > $SHM
[[ "$ip" == "$oIp" ]]&&{ (($debug))&&echo -e "$msg\n✓ Tất cả IP không thay đổi\n">>$log||echo 'nochg';exit 1; }
BASE_URL='https://api.cloudflare.com/client/v4/zones'
H1="X-Auth-Email:$AUTH_EMAIL"
H2="X-Auth-Key:$AUTH_KEY"
H3="Content-Type:application/json"
ZID=$(curl -s -G -H "$H1" -H "$H2" -H "$H3" "$BASE_URL" \
|jq -r '.result|.[]|select(.status=="active")|with_entries(select(.key == ("id","name")))|"\(.id)=\(.name)"')
[ -z "$ZID" ]&&{ (($debug))&&echo -e "$msg\nLỗi đăng nhập">>$log||echo 'badauth';exit 1; }
for z in $ZID; do
n="${z#*=}"
## Bỏ qua domain
[[ ! -z "$re" && "$n" =~ $re ]]&&{ msg="$msg\n[Domain $n] $E";continue; }
zid=${z%=*}
## Lấy DNS record A của $zId
rs=$(curl -s -G -H "$H1" -H "$H2" -H "$H3" "${BASE_URL}/$zid/dns_records?type=A" \
|jq --unbuffered -r '.result|.[]|[.id+"="+.name+"="+.content]|@tsv')
[ -z "$rs" ]||msg="$msg\n[Domain $n]"
s=''
for rc in $rs; do
IFS='=' read -r id n cIP <<<"$rc"; unset IFS
## Bỏ qua subdomain
[[ ! -z "$re" && "$n" =~ $re ]]&&{ s="${s} ░ ${n}: $E\n";continue; }
## Kiểm tra IP
if [ "$ip" == "$cIP" ];then
r="✓ IP $t không thay đổi"
else
ret=$(curl -s -X PUT -H "$H1" -H "$H2" -H "$H3" "${BASE_URL}/$zid/dns_records/$id" \
--data "{\"type\":\"A\",\"name\":\"$n\",\"content\":\"$ip\",\"ttl\":120,\"proxied\":false}" \
|jq -r '.success')
[ "$ret" == 'true' ]&&r="$cIP → $ip"||r="⛔ Lỗi!"
fi
[ ! -z "$r" ]&&s="$s ░ ${n}: $r\n"
done
[ ! -z "$s" ]&&msg="$msg\n${s::-2}"
done
(( $debug ))&&echo -e "$msg\n">>$log||echo 'good'
Script cfip_all.php
PHP script có nhiệm vụ tương tự nhưng có vẻ chậm hơn Bash script.
#!/usr/bin/php
<?php
/*
cfip_all.php
© 2020 LNT <lnt@ly-le.info>
version 20231001
Cập nhật IP cho tất cả record A thuộc một tài khoản cloudflare.com
*/
if ($argc !== 5 || count($argv) != 5) {
echo 'Không đủ/Sai tham số';
exit();
}
$cf = new cfDDNS($argv);
$cf->updateDNS();
class cfDDNS {
$this->authMail = (string) $argv[1];
$this->authKey = (string) $argv[2];
$xS = (string) $argv[3];
$xH = preg_split('/(---)/', $xS, -1, PREG_SPLIT_NO_EMPTY);
$this->xPat = ($xH) ? '/(' . implode('|', $xH) . ')/' : '';
$this->ip = (string) $argv[4];
$json = $this->cfApi('GET');
$this->zIds = [];
foreach ($json['result'] as $r) {
if ($r['status'] == 'active') {
$this->zIds["$r[id]"] = "$r[name]";
}
}
}
function updateDNS() {
// Bỏ qua Domain
foreach ($this->zIds as $zid => $name) {
if ("$this->xPat" && preg_match("$this->xPat", "$name")) continue;
// Lấy DNS record A của $zid
$json = $this->cfApi('GET', "/$zid/dns_records?type=A");
foreach ($json['result'] as $r) {
$id = $r['id'];
$name = $r['name'];
$cIp = $r['content'];
// Bỏ qua subdomain
if ("$this->xPat" && preg_match("$this->xPat", "$name")) continue;
// Kiểm tra IP
if ("$this->ip" != "$cIp") {
$data = ["type"=>"A","name"=>"$name","content"=>"$this->ip","ttl"=>240,"proxied"=>false];
$json = $this->cfApi('PUT', "/$zid/dns_records/$id", $data);
if ($json['success'] != 'true') exit('badagent');
}
}
}
exit('good');
}
function cfApi($method, $path = '', $data = []) {
$options = [
CURLOPT_URL => 'https://api.cloudflare.com/client/v4/zones' . $path,
CURLOPT_HTTPHEADER => ["X-Auth-Email:$this->authMail", "X-Auth-Key:$this->authKey", "Content-Type: application/json"],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_VERBOSE => false,
];
if(empty($method)){
$this->badParam('Empty method');
}
switch($method) {
case "GET":
$options[CURLOPT_HTTPGET] = true;
break;
case "PUT":
$options[CURLOPT_POST] = false;
$options[CURLOPT_HTTPGET] = false;
$options[CURLOPT_CUSTOMREQUEST] = "PUT";
$options[CURLOPT_POSTFIELDS] = json_encode($data);
break;
}
$req = curl_init();
curl_setopt_array($req, $options);
$res = curl_exec($req);
curl_close($req);
return json_decode($res, true);
}
}
?>
Cài đặt
Để tiện theo dõi và chỉnh sửa, đặt script ở thư mục home của người dùng, tên script cfip_all.sh
# chmod +x /volume1/homes/myusr/cfip_all.sh
# cat <<EOT >> /etc/ddns_provider.conf
[USER_Cloudflare_All]
queryurl=https://www.cloudflare.com/
modulepath=/volume1/homes/myusr/cfip_all.sh
EOT
Sau đó, vào Control Panel > External Access > DDNS > Add.
Điền tham số như hình trên.
- 2 là các domain/subdomain có IP tĩnh, không cập nhật. Nếu không có thì ghi tên một domain không tồn tại như notfound.net
- 3 và 4 là tài khoản và API Token (All zones)