1.前言
目前動態 DNS 兩大主流,一個是 BIND (ISC),另一個就是套接 DB 的 DNS 如 PowerDNS (或 mydns)
等,兩種方式各有好壞,主要是因為 BIND 會有一些復雜性,但效果非常好,而 PowerDNS 則是很簡單,
但相對的它不能承受大量查詢,主要原因在于數據庫上先天的限制. 本文主要為介紹 BIND 之動態
DNS 做法,而這個做法之最重要重點則在于nsupdate 這個指令及 IXFR (incremental zone transfer
request),是不同于傳統的 AXFR (full zone transfer),IXFR 在做 Zone Transfer (DNS 的同步機制)
時,會以差異化的部份進行同步, 而 AXFR 則是以整個 Zone 進行同步.DDNS 主要由 RFC 2136 構成,
建議若您要對 DDNS 有一定深入的了解,可以閱讀這篇 RFC 以了解更多重要的信息
(1034 1035 1995 是 2136 的基礎)
本文適用于對 DNS 巳有一定了解的朋友,若是不甚清楚建議您可先參考 TWNIC 所做的講義:
http://dns-learning.twnic.net.tw/DNS94/
如果你想對 nsupdate + key 的方法有更深入的了解可以參考
http://www.study-area.org/tips/tipsfr1.htm
或參考 isc bind 的文件有最詳細的解說
http://www.isc.org/sw/bind/arm93/Bv9ARM.pdf
本文不討論 view 的情形,若是 view 情形您必需從 view 的 match-client 去更新或是使用不同
的 key,所以建議您多參考 isb bind 的文件,雖然辛苦些,但數據絕對是最官方最正確的
2. 必要的信息及知識
本文的范例以 Shell Script 做成,重點在于原理,采用什么工具或做法完全視您個人的能力.以下
就一些重點進行說明.
2.1 BIND 動態更新
基本上在 BIND8,BIND9 都是支持 nsupdate 的,但這里面要注意的是 BIND8 在8.3.X 后才支援
IXFR,而 BIND9 則都支持,所以若您的 Server 在 8.3.0 前的版本,那就不建議了,更何況這個以
前的版本多多少少都有許多安全性的問題.
2.1.1 nsupdate:
bind 要開 allow-update 選項,讓你的程序可以來執行更新指令,allow-update 選項可以是 IP 或
key,而本文僅就 IP進行介紹,若用 Key 對有些朋友來說可能會變得稍復雜些了
CODE:[Copy to clipboard]# named.conf
# 其它略
zone "dyndns.twnic.tw" {
type master;
file "dyndns.twnic.tw";
allow-update {127.0.0.1;}; # 開放 127.0.0.1 進行動態更新
allow-transfer { slave_ip;127.0.0.1;}; # slave 主機,可能一部或多部,若無請寫 none
};
以上是開放讓 127.0.0.1 進行動態更新,動態更新有其指令,詳細您可看看 nsupdate man page
(man nsupdate),以下僅以最常用的進行說明:
CODE:[Copy to clipboard]#nsupdate
[root@eai1 dyndns]# nsupdate
> server 127.0.0.1
> zone dyndns.twnic.tw
> update delete user1.dyndns.twnic.tw A 211.72.210.249
> update add user1.dyndns.twnic.tw A 211.72.210.251
ttl 'A': not a valid number # 這個例子是錯誤示范,加一筆記錄要有 TTL 值
> update add user1.dyndns.twnic.tw 60 A 211.72.210.251
> show
Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 0
;; flags: ; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
;; UPDATE SECTION:
user1.dyndns.twnic.tw. 0 NONE A 211.72.210.249
user1.dyndns.twnic.tw. 60 IN A 211.72.210.251
> send
> quit
CODE:[Copy to clipboard]server 指向某一臺 NameServer 進行 update 操作
zone 修改某個 zone file
update delete 進行 update 的 delete 動作,這個指令格式是
update delete FQDN TYPE RDATA,如果有多筆相同的 A
記錄或不同的 MX 記錄要刪除某一筆需將 RDATA 補上,
若沒有 RDATA 則表示這個 FQDN 的這個 TYPE 都要刪除
(TYPE 即是 A,MX,PTR,SOA,NS..等,RDATA 就是 TYPE 后
接的東西,如 A 的 RDATA 是 IP 而 MX 的 RDATA 是 優
先權 FQDN)
update add 進行記錄的增加,操作如同 delete, 但是一定要有
TTL 值,且必需明確寫出 RDATA
show 這只是操作后要顯示進行了那些 update 指令
send 這個代表要把整個 update 指令送給 server,操作 update
時數據不是馬上送出的,所以 update 可以很多行,最后
nsupdate 看到 send 時,才會將整個所有 update 送出,
而 update 使用 port 53/udp 若送出的數據量 (DNS packet)
大于 512 bytes,則會 truncate 而改使用 53/tcp,這是您
需要注意的地方,而只要您有 send, 則 SOA 記錄的 serial
會自動加1,以期讓 slave 來進行同步,所以過多的 send 可
能造成過多的 traffic
2.1.2 zone file 及日志文件
如果您進行了 nsupdate 的操作,則原來 directory 所指的目錄將會產生一些日志文件,這個日志文件即為
zone_name.jnl (directory 習慣上都設在 /var/named , 您自己必要注意 named 程序要有寫入的權限,
chroot 狀況等)
CODE:[Copy to clipboard]# 顯示 dyndns.twnic.tw zone file 內容
[root@eai1 named]# cat /var/named/dyndns.twnic.tw
$TTL 86400 ; 1 day
@ IN SOA twnic.net.tw. snw.twnic.net.tw. (
2006073267 ; serial
7200 ; refresh (2 hours)
1800 ; retry (30 minutes)
2419000 ; expire (3 weeks 6 days 23 hours 56 minutes 40 seconds)
300 ; minimum (5 minutes)
)
NS ns2.dyndns.twnic.tw.
NS eai1.twnic.tw.
ns2 A 203.73.24.204
; 如果別人在查詢時,我們有這個記錄則響應這個記錄的 IP,若沒有這個記錄代表
; 這個網站沒有上線,所以此時我們可以建立一筆 wildcard 記錄,指向自己的說
; 明網站,以供 user 識別這個網站沒有上線,而這筆 wildcard 的記錄 TTL 時間
; 不能太長,以免別的 DNS Cache 了這資料
* 0 A 211.72.210.251
# 目錄里的東西
[root@eai1 named]# ls -la /var/named/
總計 128
drwxr-xr-x 2 named named 4096 7月 27 09:25 .
drwxr-xr-x 20 root root 4096 3月 13 16:50 ..
-rw-r--r-- 1 named named 451 7月 27 09:25 dyndns.twnic.tw
-rw-r--r-- 1 named named 104177 7月 27 10:01 dyndns.twnic.tw.jnl
-rw-r--r-- 1 named named 195 7月 4 2001 localhost.zone
-rw-r--r-- 1 named named 2851 10月 17 2003 named.ca
我們可以看到這個 .jnl 的產生,這個 .jnl 檔是不能隨便刪除,因為它等于是 dyndns.twnic.tw 的補
充數據,而這些補充資料在 DNS reload/restart 時, named 還會在把它讀進來,所以動態更新后的數據,并不會隨著 dns 重啟后而消失,如果你想讓現在整個 zone 文件出現所有的記錄,那可以 rndc stop 來停止 dns, 此時 named 會把 .jnl數據寫入原來的 zone file,不過這種方式一般來說較不建議,因為我們的 zonefile 只要存一個樣版 (template),其它的東西都是臨時性的. 而若你想知道現在整個 zone 的內容,在可以做 zone transfer 的主機上(如上例為 slave_ip及 127.0.0.1), 以 dig 指令來執行 axfr:
CODE:[Copy to clipboard][root@eai1 dyndns]# dig @127.0.0.1 dyndns.twnic.tw axfr
; <<>> DiG 9.3.0 <<>> @127.0.0.1 dyndns.twnic.tw axfr
;; global options: printcmd
dyndns.twnic.tw. 86400 IN SOA twnic.net.tw. snw.twnic.net.tw. 2006073528 7200 1800 2419000 300
dyndns.twnic.tw. 86400 IN NS ns2.dyndns.twnic.tw.
dyndns.twnic.tw. 86400 IN NS eai1.twnic.tw.
*.dyndns.twnic.tw. 0 IN A 211.72.210.251
ns2.dyndns.twnic.tw. 86400 IN A 203.73.24.204
user1.dyndns.twnic.tw. 60 IN A 211.72.210.248
user1.dyndns.twnic.tw. 60 IN MX 10 user1.dyndns.twnic.tw.
# 以下略
...
2.設定 NameServer 僅進行差異化的同步
Master/Slave 要進行 zone file 的同步,而 ddns server 若只有一部是可以不用考慮這些問題的,但是若有兩部以上的 DNS Server, 就需要考慮到同步的進行方式,若 zone file 的總數據量小,采用什么同步方式是無所謂的,但若數據量多,或是經常處在變動狀況,那差異化的同步就會顯得很重要,因為它可以讓所有的 DNS 在短時間內全部同步完成,BIND 支持 IXFR 后,其預設即是采用 IXFR, 若沒有 IXFR (update) 時,則采用 AXFR,所以不需要對 IXFR 進行額外設定,但為使大家了解其參數,本處還是舉例來進行說明,好讓大家能夠更了解
CODE:[Copy to clipboard]# master DNS's named.conf
key "rndc-key" {
algorithm hmac-md5;
secret "HpXtFRFdLaRPFjpZokIwusyezyyRNjxhcafCfmktWNyGkDFzHAXlpTZQtVLc";
};
controls {
inet 127.0.0.1 port 953
allow { 127.0.0.1; } keys { "rndc-key"; };
};
options {
directory "/var/named";
pid-file "/var/run/named/named.pid";
allow-transfer { none; };
provide-ixfr yes; # 提供 slave 主機以 IXFR 同步,default yes
request-ixfr yes; # slave 以 IXFR 向 master 進行同步,default yes
recursion no; # 不允許遞歸查詢
};
zone "0.0.127.in-addr.arpa" {
type master;
file "named.local";
};
zone "." {
type hint;
file "named.ca";
};
zone "dyndns.twnic.tw" {
type master;
file "dyndns.twnic.tw";
max-journal-size 500k; # 設定日志文件大小
allow-transfer { 203.73.24.204;127.0.0.1;}; # 可做 ixfr/axfr 的來源 IP,必需寫上 slave,
# 127.0.0.1 是方使我們自己查看 zone file 現況
also-notify {203.73.24.205;211.72.210.251}; # 額外的同步主機,這可能是 hot site 備份主機
allow-update { 127.0.0.1;}; # 允許動態更新的來源
};
上述的東西相信只要對 BIND DNS 有一定了解的朋友應該都是沒有問題的,較不常出現的項目我都加上了的批注以利大家了解,而 slave 的設法都同于 master, 只有在 zone 的部份稍有不同:
CODE:[Copy to clipboard]# slave DNS's named.conf
# 其它設定皆同上,只有 zone ...稍有不同,同于一般的 slave zone,不需要再開 allow-update (default none)
zone "dyndns.twnic.tw" {
type slave;
masters {211.72.210.249;};
file "dyndns.twnic.tw";
allow-transfer { none;};
};
所以從上述我們可以知道 DDNS for Master/Slave 您只要對 master 進行 update,每次的 update 送出 (send) 會使該zone 的序號加一,只要 zone 有更新 , dns 會送出 notify 訊息給所有的 NS 主機(NS 記錄上所列的名稱服務器),及可能的 also-notify 對象,使 slave 主機知道要進行同步,同步時優先采用 IXFR,若 master 不支持 IXFR 則改使用 AXFR,以達到即使更新,及時同步的效果
此外,有些做 DDNS 的公司可能對 IXFR 不了解,而是把所有的 zone type 都設成了 MASTER,然后對這些 NameServer進行 nsupdate , 這種做法也是可以的,不過中間若漏了一步或那一臺少做了一件事,那兩邊的數據就會不一致,導致可能同一個名稱會有不同的解析結果 (例如 gnway.net 做法)
3. DDNS 的前端及后臺控制范例
說是范例主要是讓大家參考原理,并沒有必要一定都用我的方式,只要前面講的東西您可以了解,程控的部份僅是末節,以下僅列出我所用的方式供大家參考
3.1 MYSQL table
主要由三個表構成,分別為 RR (Resource Record), RR_LOG (舊資料,建議您依狀況適當保存),USER (user 認證)
CODE:[Copy to clipboard]CREATE TABLE RR (
SN int(20) NOT NULL auto_increment,
USERNAME varchar(64) NOT NULL default '',
FQDN varchar(64) NOT NULL default '',
TTL int(5) NOT NULL default '60',
TYPE varchar(10) NOT NULL default '',
RDATA varchar(64) NOT NULL default '',
CREATE_TIME timestamp(14) NOT NULL,
PRIMARY KEY (SN),
KEY USERNAME (USERNAME),
KEY FQDN (FQDN),
KEY TTL (TTL),
KEY TYPE (TYPE),
KEY CREATE_TIME (CREATE_TIME)
) TYPE=MyISAM;
--
-- Table structure for table 'RR_LOG'
--
CREATE TABLE RR_LOG (
SN int(20) NOT NULL default '0',
USERNAME varchar(64) NOT NULL default '',
FQDN varchar(64) NOT NULL default '',
TTL int(5) NOT NULL default '60',
TYPE varchar(10) NOT NULL default '',
RDATA varchar(64) NOT NULL default '',
CREATE_TIME varchar(14) default NULL,
PRIMARY KEY (SN),
KEY USERNAME (USERNAME),
KEY FQDN (FQDN),
KEY CREATE_TIME (CREATE_TIME)
) TYPE=MyISAM;
--
-- Table structure for table 'USER'
--
CREATE TABLE USER (
SN int(20) NOT NULL auto_increment,
USERNAME varchar(64) NOT NULL default '',
PASSWD varchar(64) NOT NULL default '',
EMAIL varchar(64) NOT NULL default '',
MEMO varchar(255) NOT NULL default '',
PRIMARY KEY (SN),
UNIQUE KEY USERNAME (USERNAME)
) TYPE=MyISAM;
3.2 dyndns.cfg 設定檔
這個設定文件主要為了給 CGI 程序及產生 nsupdate 的程序 (dyndns-cron.sh) 所使用,透過 eval 方式來執行,
以取得共同的變量
CODE:[Copy to clipboard]# mysql host/db/user/password
DBHOST=localhost
DBNAME=dyndns
DBUSER=UserName
DBPASS=Your_Passwd
MYSQL="mysql $DBNAME -h $DBHOST -u $DBUSER -p$DBPASS"
# dyndns domain
DOMAIN=dyndns.twnic.tw
# Master IP
DYNDNS_MASTER=127.0.0.1
# nsupdate command file
CMD_FILE=/tmp/nsupdate.cmd
# update freqency
UPD_FREQ=15
# RR valid time (seconds),default 20 mins
RR_ALIVE=1200
3.3 dyndns.cgi CGI 程序
這個 CGI 主要用于接收 USER 端來的信息,驗證通過后即為把 USERNAME.DOMAIN 資料,A/MX 及對應 IP 存入Table RR 中,此外這個 CGI 以 shell script 做成,可以于多數人的環境執行 (chmod 755 及目錄的 CGI 執行權限 ExecCGI 莫忘)
CODE:[Copy to clipboard]#!/bin/sh
echo -ne "Content-Type: text/html "
if [ -n "$QUERY_STRING" ];then
# 取得 QUERY_STRING,以下這個作法是危險的,因為沒有檢查數據的正確性就 eval
# 我的用意只在于說明作法
eval `echo "$QUERY_STRING" | sed "s/&/;/g"`
# 讀取設定文件,這個路徑您需要自行調整
eval `cat /home/abelyang/dyndns/dyndns.cfg `
sql0="select 1 from USER where USERNAME='$LOGIN' and PASSWD='$PASSWD'"
res=`echo $sql0 | $MYSQL `
# 如果 USER 密碼正確, ${#res} 應為2,不對則為 0
if [ ${#res} -lt 1 ];then
echo "Login Failure"
else
# 取得 IP, 需判斷有 Proxy 存在,但是不考慮 Proxy 后是 NAT 情形
IP=${HTTP_X_FORWARDED_FOR:-$REMOTE_ADDR}
FQDN="$LOGIN.$DOMAIN"
# 刪除上一次的登入
sql1="delete from RR where USERNAME='$LOGIN'"
# 預設的動態更新項目為 A/MX
sql2="insert into RR(USERNAME,FQDN,TYPE,RDATA) values('$LOGIN','$FQDN','A','$IP')"
sql3="insert into RR(USERNAME,FQDN,TYPE,RDATA) values('$LOGIN','$FQDN','MX','10 $FQDN')"
echo $sql1 | $MYSQL
echo $sql2 | $MYSQL
echo $sql3 | $MYSQL
echo $LOGIN login success @$IP
fi
else
# 以下只是網頁的部份,我沒有做 DDNS 申請,這個部份我想只要懂網頁的朋友應該都會才是
cat <