1.前言
目前動(dòng)態(tài) DNS 兩大主流,一個(gè)是 BIND (ISC),另一個(gè)就是套接 DB 的 DNS 如 PowerDNS (或 mydns)
等,兩種方式各有好壞,主要是因?yàn)?BIND 會(huì)有一些復(fù)雜性,但效果非常好,而 PowerDNS 則是很簡單,
但相對(duì)的它不能承受大量查詢,主要原因在于數(shù)據(jù)庫上先天的限制. 本文主要為介紹 BIND 之動(dòng)態(tài)
DNS 做法,而這個(gè)做法之最重要重點(diǎn)則在于nsupdate 這個(gè)指令及 IXFR (incremental zone transfer
request),是不同于傳統(tǒng)的 AXFR (full zone transfer),IXFR 在做 Zone Transfer (DNS 的同步機(jī)制)
時(shí),會(huì)以差異化的部份進(jìn)行同步, 而 AXFR 則是以整個(gè) Zone 進(jìn)行同步.DDNS 主要由 RFC 2136 構(gòu)成,
建議若您要對(duì) DDNS 有一定深入的了解,可以閱讀這篇 RFC 以了解更多重要的信息
(1034 1035 1995 是 2136 的基礎(chǔ))
本文適用于對(duì) DNS 巳有一定了解的朋友,若是不甚清楚建議您可先參考 TWNIC 所做的講義:
http://dns-learning.twnic.net.tw/DNS94/
如果你想對(duì) nsupdate + key 的方法有更深入的了解可以參考
http://www.study-area.org/tips/tipsfr1.htm
或參考 isc bind 的文件有最詳細(xì)的解說
http://www.isc.org/sw/bind/arm93/Bv9ARM.pdf
本文不討論 view 的情形,若是 view 情形您必需從 view 的 match-client 去更新或是使用不同
的 key,所以建議您多參考 isb bind 的文件,雖然辛苦些,但數(shù)據(jù)絕對(duì)是最官方最正確的
2. 必要的信息及知識(shí)
本文的范例以 Shell Script 做成,重點(diǎn)在于原理,采用什么工具或做法完全視您個(gè)人的能力.以下
就一些重點(diǎn)進(jìn)行說明.
2.1 BIND 動(dòng)態(tài)更新
基本上在 BIND8,BIND9 都是支持 nsupdate 的,但這里面要注意的是 BIND8 在8.3.X 后才支援
IXFR,而 BIND9 則都支持,所以若您的 Server 在 8.3.0 前的版本,那就不建議了,更何況這個(gè)以
前的版本多多少少都有許多安全性的問題.
2.1.1 nsupdate:
bind 要開 allow-update 選項(xiàng),讓你的程序可以來執(zhí)行更新指令,allow-update 選項(xiàng)可以是 IP 或
key,而本文僅就 IP進(jìn)行介紹,若用 Key 對(duì)有些朋友來說可能會(huì)變得稍復(fù)雜些了
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 進(jìn)行動(dòng)態(tài)更新
allow-transfer { slave_ip;127.0.0.1;}; # slave 主機(jī),可能一部或多部,若無請(qǐng)寫 none
};
以上是開放讓 127.0.0.1 進(jìn)行動(dòng)態(tài)更新,動(dòng)態(tài)更新有其指令,詳細(xì)您可看看 nsupdate man page
(man nsupdate),以下僅以最常用的進(jìn)行說明:
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 # 這個(gè)例子是錯(cuò)誤示范,加一筆記錄要有 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 指向某一臺(tái) NameServer 進(jìn)行 update 操作
zone 修改某個(gè) zone file
update delete 進(jìn)行 update 的 delete 動(dòng)作,這個(gè)指令格式是
update delete FQDN TYPE RDATA,如果有多筆相同的 A
記錄或不同的 MX 記錄要?jiǎng)h除某一筆需將 RDATA 補(bǔ)上,
若沒有 RDATA 則表示這個(gè) FQDN 的這個(gè) TYPE 都要?jiǎng)h除
(TYPE 即是 A,MX,PTR,SOA,NS..等,RDATA 就是 TYPE 后
接的東西,如 A 的 RDATA 是 IP 而 MX 的 RDATA 是 優(yōu)
先權(quán) FQDN)
update add 進(jìn)行記錄的增加,操作如同 delete, 但是一定要有
TTL 值,且必需明確寫出 RDATA
show 這只是操作后要顯示進(jìn)行了那些 update 指令
send 這個(gè)代表要把整個(gè) update 指令送給 server,操作 update
時(shí)數(shù)據(jù)不是馬上送出的,所以 update 可以很多行,最后
nsupdate 看到 send 時(shí),才會(huì)將整個(gè)所有 update 送出,
而 update 使用 port 53/udp 若送出的數(shù)據(jù)量 (DNS packet)
大于 512 bytes,則會(huì) truncate 而改使用 53/tcp,這是您
需要注意的地方,而只要您有 send, 則 SOA 記錄的 serial
會(huì)自動(dòng)加1,以期讓 slave 來進(jìn)行同步,所以過多的 send 可
能造成過多的 traffic
2.1.2 zone file 及日志文件
如果您進(jìn)行了 nsupdate 的操作,則原來 directory 所指的目錄將會(huì)產(chǎn)生一些日志文件,這個(gè)日志文件即為
zone_name.jnl (directory 習(xí)慣上都設(shè)在 /var/named , 您自己必要注意 named 程序要有寫入的權(quán)限,
chroot 狀況等)
CODE:[Copy to clipboard]# 顯示 dyndns.twnic.tw zone file 內(nèi)容
[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
; 如果別人在查詢時(shí),我們有這個(gè)記錄則響應(yīng)這個(gè)記錄的 IP,若沒有這個(gè)記錄代表
; 這個(gè)網(wǎng)站沒有上線,所以此時(shí)我們可以建立一筆 wildcard 記錄,指向自己的說
; 明網(wǎng)站,以供 user 識(shí)別這個(gè)網(wǎng)站沒有上線,而這筆 wildcard 的記錄 TTL 時(shí)間
; 不能太長,以免別的 DNS Cache 了這資料
* 0 A 211.72.210.251
# 目錄里的東西
[root@eai1 named]# ls -la /var/named/
總計(jì) 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
我們可以看到這個(gè) .jnl 的產(chǎn)生,這個(gè) .jnl 檔是不能隨便刪除,因?yàn)樗扔谑?dyndns.twnic.tw 的補(bǔ)
充數(shù)據(jù),而這些補(bǔ)充資料在 DNS reload/restart 時(shí), named 還會(huì)在把它讀進(jìn)來,所以動(dòng)態(tài)更新后的數(shù)據(jù),并不會(huì)隨著 dns 重啟后而消失,如果你想讓現(xiàn)在整個(gè) zone 文件出現(xiàn)所有的記錄,那可以 rndc stop 來停止 dns, 此時(shí) named 會(huì)把 .jnl數(shù)據(jù)寫入原來的 zone file,不過這種方式一般來說較不建議,因?yàn)槲覀兊?zonefile 只要存一個(gè)樣版 (template),其它的東西都是臨時(shí)性的. 而若你想知道現(xiàn)在整個(gè) zone 的內(nèi)容,在可以做 zone transfer 的主機(jī)上(如上例為 slave_ip及 127.0.0.1), 以 dig 指令來執(zhí)行 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.設(shè)定 NameServer 僅進(jìn)行差異化的同步
Master/Slave 要進(jìn)行 zone file 的同步,而 ddns server 若只有一部是可以不用考慮這些問題的,但是若有兩部以上的 DNS Server, 就需要考慮到同步的進(jìn)行方式,若 zone file 的總數(shù)據(jù)量小,采用什么同步方式是無所謂的,但若數(shù)據(jù)量多,或是經(jīng)常處在變動(dòng)狀況,那差異化的同步就會(huì)顯得很重要,因?yàn)樗梢宰屗械?DNS 在短時(shí)間內(nèi)全部同步完成,BIND 支持 IXFR 后,其預(yù)設(shè)即是采用 IXFR, 若沒有 IXFR (update) 時(shí),則采用 AXFR,所以不需要對(duì) IXFR 進(jìn)行額外設(shè)定,但為使大家了解其參數(shù),本處還是舉例來進(jìn)行說明,好讓大家能夠更了解
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 主機(jī)以 IXFR 同步,default yes
request-ixfr yes; # slave 以 IXFR 向 master 進(jìn)行同步,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; # 設(shè)定日志文件大小
allow-transfer { 203.73.24.204;127.0.0.1;}; # 可做 ixfr/axfr 的來源 IP,必需寫上 slave,
# 127.0.0.1 是方使我們自己查看 zone file 現(xiàn)況
also-notify {203.73.24.205;211.72.210.251}; # 額外的同步主機(jī),這可能是 hot site 備份主機(jī)
allow-update { 127.0.0.1;}; # 允許動(dòng)態(tài)更新的來源
};
上述的東西相信只要對(duì) BIND DNS 有一定了解的朋友應(yīng)該都是沒有問題的,較不常出現(xiàn)的項(xiàng)目我都加上了的批注以利大家了解,而 slave 的設(shè)法都同于 master, 只有在 zone 的部份稍有不同:
CODE:[Copy to clipboard]# slave DNS's named.conf
# 其它設(shè)定皆同上,只有 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 您只要對(duì) master 進(jìn)行 update,每次的 update 送出 (send) 會(huì)使該zone 的序號(hào)加一,只要 zone 有更新 , dns 會(huì)送出 notify 訊息給所有的 NS 主機(jī)(NS 記錄上所列的名稱服務(wù)器),及可能的 also-notify 對(duì)象,使 slave 主機(jī)知道要進(jìn)行同步,同步時(shí)優(yōu)先采用 IXFR,若 master 不支持 IXFR 則改使用 AXFR,以達(dá)到即使更新,及時(shí)同步的效果
此外,有些做 DDNS 的公司可能對(duì) IXFR 不了解,而是把所有的 zone type 都設(shè)成了 MASTER,然后對(duì)這些 NameServer進(jìn)行 nsupdate , 這種做法也是可以的,不過中間若漏了一步或那一臺(tái)少做了一件事,那兩邊的數(shù)據(jù)就會(huì)不一致,導(dǎo)致可能同一個(gè)名稱會(huì)有不同的解析結(jié)果 (例如 gnway.net 做法)
3. DDNS 的前端及后臺(tái)控制范例
說是范例主要是讓大家參考原理,并沒有必要一定都用我的方式,只要前面講的東西您可以了解,程控的部份僅是末節(jié),以下僅列出我所用的方式供大家參考
3.1 MYSQL table
主要由三個(gè)表構(gòu)成,分別為 RR (Resource Record), RR_LOG (舊資料,建議您依狀況適當(dāng)保存),USER (user 認(rèn)證)
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 設(shè)定檔
這個(gè)設(shè)定文件主要為了給 CGI 程序及產(chǎn)生 nsupdate 的程序 (dyndns-cron.sh) 所使用,透過 eval 方式來執(zhí)行,
以取得共同的變量
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 程序
這個(gè) CGI 主要用于接收 USER 端來的信息,驗(yàn)證通過后即為把 USERNAME.DOMAIN 資料,A/MX 及對(duì)應(yīng) IP 存入Table RR 中,此外這個(gè) CGI 以 shell script 做成,可以于多數(shù)人的環(huán)境執(zhí)行 (chmod 755 及目錄的 CGI 執(zhí)行權(quán)限 ExecCGI 莫忘)
CODE:[Copy to clipboard]#!/bin/sh
echo -ne "Content-Type: text/html "
if [ -n "$QUERY_STRING" ];then
# 取得 QUERY_STRING,以下這個(gè)作法是危險(xiǎn)的,因?yàn)闆]有檢查數(shù)據(jù)的正確性就 eval
# 我的用意只在于說明作法
eval `echo "$QUERY_STRING" | sed "s/&/;/g"`
# 讀取設(shè)定文件,這個(gè)路徑您需要自行調(diào)整
eval `cat /home/abelyang/dyndns/dyndns.cfg `
sql0="select 1 from USER where USERNAME='$LOGIN' and PASSWD='$PASSWD'"
res=`echo $sql0 | $MYSQL `
# 如果 USER 密碼正確, ${#res} 應(yīng)為2,不對(duì)則為 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'"
# 預(yù)設(shè)的動(dòng)態(tài)更新項(xiàng)目為 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
# 以下只是網(wǎng)頁的部份,我沒有做 DDNS 申請(qǐng),這個(gè)部份我想只要懂網(wǎng)頁的朋友應(yīng)該都會(huì)才是
cat <