cloudflare.com không phải chuyên về dịch vụ Dynamic DNS – hỗ trợ cập nhật IP động cho tên miền, nhưng nó có thư viện API mạnh giúp thêm/bớt/sửa đổi DNS record (không chỉ là A record) nên về mặt ứng dụng, cloudflare cũng là một công ty dịch vụ về DDNS. Nhưng vì không chuyên, việc thay đổi IP cho DNS record A của cloudflare khó hơn nhiều dịch vụ DDNS khác.
Để sử dụng hàm API của cloudflare, chúng ta cần các tham số
AUTH_EMAIL='email@domain.com'
AUTH_KEY='4b9ff1eff6eabcdef12d9fe70fd60a8803e94'
AUTH_EMAIL là email dùng để đăng nhập vào cloudflare, AUTH_KEY tìm thấy ở My Profile – API Tokens – API Keys – Global API Key, bấm view, nhập mật khẩu đăng nhập để xem/copy Global API Key
Ngoài ra, còn cần đến Zone Id – danh biểu lập trình của domain, đã được cloudflare tạo sẵn cho từng domain, nhưng chúng ta sẽ tự tìm lấy trong mã lập trình để không phụ thuộc vào một domain cụ thể. Tương tự, RECORD_ID là danh biểu lập trình của subdomain, cũng được tìm bằng mã lập trình.
Cú pháp dùng bash script cfIP để cập nhật IP cho subdomain
cfIP domain subdomain
Trong đó sub_domain có thể viết tắt mail thay cho mail.domain.com, mặc dù API cloudflare chờ đợi tên đầy đủ. Mặt khác, nếu chỉ dùng subdomain trên tham số dòng lệnh thì khó lòng tìm ra domain chính là gì. Thí dụ với mx.mail.domain.com domain chính có thể là mail.domain.com hoặc domain.com
Mở đầu của script như sau:
#!/bin/bash
#!/bin/bash
# cfIP
# Update dynamic IP for DNS record A on cloudflare
# © 2020 LNT <lnt@ly-le.info
# version 20200420
#
AUTH_EMAIL='email@domain.com'
AUTH_KEY='4b9ff1eff6eabcdef12d9fe70fd60a8803e94'
# rút gọn URL
BASE_URL='https://api.cloudflare.com/client/v4/zones/'
# lưu IP lần trước
TMP='/tmp/cloudflare.ip'
if [ $# -ne 2 ]; then
echo "Cập nhật IP subdomain của cloudflare"
echo "Cú pháp: $(basename $0) domain subdomain"
exit 1
fi
DOMAIN="$1"
DNS_RECORD="$2"
# thêm domain.com nếu cần
[ "${DNS_RECORD%'$DOMAIN'}" = "${DNS_RECORD}" ] && DNS_RECORD="${DNS_RECORD}.${DOMAIN}"
1. Chúng ta tìm WAN IP để cập nhật IP động. Có rất nhiều trang giúp tìm wan ip như icanhazip.com, checkip.dyndns.org, checkip.amazonaws.com, v4.ifconfig.co, myip.dnsomatic.com, … Chúng ta chọn trang nào trả kết quả dạng text thuần túy và đáp ứng nhanh, đồng thời phải kiểm tra IP nhận được vì nếu server bận thì thay vì IP chúng ta có một thông báo lỗi.
# WAN IP
IP=$(curl -s "http://icanhazip.com")
[[ "${IP}" =~ ^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]* ]] || { echo "Bad IP"; exit 1; }
# so với IP đã lưu
[ -f "$TMP" ] && oIP=$(<$TMP) || oIP=''
printf $IP > $TMP
# nếu giống IP đã lưu, không cần cập nhật
[ "$IP" = "$oIP" ] && exit 0;
2. Tiếp theo, tìm ZONE_ID của DOMAIN
# ID của domain
ZONE_ID=$(curl -s -X GET "${BASE_URL}?name=${DOMAIN}&status=active" \
-H "X-Auth-Email: ${ACUTH_EMAIL}" \
-H "X-Auth-Key: ${AUTH_KEY}" \
-H "Content-Type: application/json" | sed -r 's/^\{"result":\[\{"id":"([0-9a-f]*)",.*$/\1/')
[[ "${ZONE_ID}" =~ ^[0-9a-f]{32}$ ]] || { echo "Bad Zone ID"; exit 1; }
3. Tìm RECORD_ID của subdomain
# ID của DNS record
RECORD_ID=$(curl -s -X GET "${BASE_URL}${ZONE_ID}/dns_records?type=A&name=${DNS_RECORD}" \
-H "X-Auth-Email: ${AUTH_EMAIL}" \
-H "X-Auth-Key: ${AUTH_KEY}" \
-H "Content-Type: application/json" | sed -r 's/^\{"result":\[\{"id":"([0-9a-f]*)",.*$/\1/')
[[ "${RECORD_ID}" =~ ^[0-9a-f]{32}$ ]] || { echo "Bad Record ID"; exit 1; }
4. Cuối cùng, cập nhật IP cho subdomain, dùng ZONE_ID và RECORD_ID
# Cập nhật IP cho subdomain
RET=$(curl -s -X PUT "${BASE_URL}${ZONE_ID}/dns_records/${RECORD_ID}" \
-H "X-Auth-Email: ${AUTH_EMAIL}" \
-H "X-Auth-Key: ${AUTH_KEY}" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"${DNS_RECORD}\",\"content\":\"${IP}\",\"ttl\":120,\"proxied\":false}" | sed -r 's/.*"success":(true|false).*/\1/')
[ "$RET" = 'true' ] && echo SUCCESS || echo FAILURE
Để cập nhật IP cho một subdomain, chúng ta gọi curl 4 lần, trong đó có 3 lần gọi hàm API của cloudflare, tuy rằng cũng không chậm. Ghép các đoạn code trên để có script hoàn chỉnh. Nhớ gán thuộc tính thực thi cho script:
sudo chmod +x ./cfIP
Cải tiến
Chúng ta có thể khiến script chạy nhanh hơn nếu giảm số lần gọi hàm API của cloudflare.com, bằng cách lưu lại các giá trị tìm được trong shared memory để dành cho lần chạy sau.
SHM=/dev/shm/cloudflare
[ ! -d $SHM ] && mkdir $SHM
# lưu RECORD_ID
echo ${RECORD_ID} > $SHM/${DNS_RECORD}
# đọc lại
RECORD_ID=$(<$SHM/${DNS_RECORD})
Script cfIP viết lại như sau
#!/bin/bash
# cfIP
# Update dynamic IP for DNS record A on cloudflare
# © 2020 LNT <lnt@ly-le.info>
# version 20200420
#
AUTH_EMAIL='email@domain.com'
AUTH_KEY='4b9ff1eff6eabcdef12d9fe70fd60a8803e94'
BASE_URL='https://api.cloudflare.com/client/v4/zones/'
if [ $# -ne 2 ]; then
echo "Cập nhật IP subdomain của cloudflare"
echo "Cú pháp: $(basename $0) domain subdomain"
exit 1
fi
DOMAIN="$1"
DNS_RECORD="$2"
[ "${DNS_RECORD%'$DOMAIN'}" = "${DNS_RECORD}" ] && DNS_RECORD="${DNS_RECORD}.${DOMAIN}"
# Shared Memory
SHM=/dev/shm/cloudflare
[ ! -d $SHM ] && mkdir $SHM
# WAN IP
IP=$(curl -s "http://icanhazip.com")
[[ "${IP}" =~ ^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]* ]] || { echo "Bad IP"; exit 1; }
[ -f "$SHM/ip" ] && oIP=$(<$SHM/ip) || oIP=''
printf $IP > $SHM/ip
[ "$IP" = "$oIP" ] && exit 0;
# ID của domain
if [ -f $SHM/$DOMAIN ]; then
ZONE_ID=$(<$SHM/$DOMAIN)
else
ZONE_ID=$(curl -s -X GET "${BASE_URL}?name=${DOMAIN}&status=active" \
-H "X-Auth-Email: ${AUTH_EMAIL}" \
-H "X-Auth-Key: ${AUTH_KEY}" \
-H "Content-Type: application/json" | sed -r 's/^\{"result":\[\{"id":"([0-9a-f]*)",.*$/\1/')
[[ "${ZONE_ID}" =~ ^[0-9a-f]{32}$ ]] || { echo "Bad Zone ID"; exit 1; }
echo ${ZONE_ID} > $SHM/$DOMAIN
fi
# ID của DNS record
if [ -f $SHM/${DNS_RECORD} ]; then
RECORD_ID=$(<$SHM/${DNS_RECORD})
else
RECORD_ID=$(curl -s -X GET "${BASE_URL}${ZONE_ID}/dns_records?type=A&name=${DNS_RECORD}" \
-H "X-Auth-Email: ${AUTH_EMAIL}" \
-H "X-Auth-Key: ${AUTH_KEY}" \
-H "Content-Type: application/json" | sed -r 's/^\{"result":\[\{"id":"([0-9a-f]*)",.*$/\1/')
[[ "${RECORD_ID}" =~ ^[0-9a-f]{32}$ ]] || { echo "Bad Record ID"; exit 1; }
echo ${RECORD_ID} > $SHM/${DNS_RECORD}
fi
# Cập nhật IP cho sub/domain
RET=$(curl -s -X PUT "${BASE_URL}${ZONE_ID}/dns_records/${RECORD_ID}" \
-H "X-Auth-Email: ${AUTH_EMAIL}" \
-H "X-Auth-Key: ${AUTH_KEY}" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"${DNS_RECORD}\",\"content\":\"${IP}\",\"ttl\":120,\"proxied\":false}" | sed -r 's/.*"success":(true|false).*/\1/')
[ "$RET" = 'true' ] && echo SUCCESS || echo FAILURE
Chú thích
- Lưu IP cục bộ nhằm kiểm tra WAN IP có thay đổi hay không để chạy script cập nhật IP. Với cloudflare có cách kiểm tra chính xác hơn là so WAN IP với IP hiện tại đang giữ trên cloudflare:
# IP hiện tại
cIP=$(curl -s -X GET "${BASE_URL}${ZONE_ID}/dns_records/${RECORD_ID}"\
-H "X-Auth-Email: ${AUTH_EMAIL}" \
-H "X-Auth-Key: ${AUTH_KEY}" \
-H "Content-Type: application/json" | grep -Po '(?<=content":")[0-9.]+')
wIP=$(curl -s "http://icanhazip.com")
[[ "${wIP}" =~ ^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]* ]] || { echo "Bad IP"; exit 1; }
[ "$wIP" = "$cIP" ] && { echo "IP has not changed!"; exit 0; }
2. Sửa code một chút để update nhiều subdomain được nhanh hơn. Update được IP cho domain chính? (Xem bài tiếp theo)