all-in-one 3 (dynamic ip script)

Topic: 
 

ทำเรื่อง dynamic dns client

สมมุติว่าได้สมัครเป็นสมาชิก ddns client ไว้ที่ www.zoneedit.com และ www.everydns.net ไว้เรียบร้อยแล้ว
การนี้เราจะใช้ทั้งคู่ในการเป็น name server ให้เรา กันเหนียวไว้ เวลาอันไหนตาย อีกอันจะได้ทำหน้าที่แทน
จากประวัติพบว่า zoneedit เสถียรและอัปเดตเร็วกว่า เราจึงให้ขึ้นเป็น primary

งานที่ต้องทำคือ

  1. สคริปต์ดูเลขไอพีจากเราเตอร์ หรืออาจให้เริ่มการทำงานเราเตอร์ใหม่
  2. สคริปต์ cron ที่จะตรวจไอพีมาเก็บไว้เป็นระยะ ทุก 5 นาที ถ้าไม่เท่าเก่าจึงสั่งอัปเดต แบบอัปเดตทุกโซน
  3. สคริปต์ cron ที่จะตรวจไอพีซ้ำ จาก dns ภายนอก ทุกครึ่งชั่วโมง ถ้าไอพีไม่ตรงค่อยสั่งอัปเดต เฉพาะโซนที่ไม่ตรง
  4. สคริปต์อัปเดต แบบมีอาร์กิวเมนต์ต่อท้ายว่าจะให้อัปเดตทั้งหมด หรือเฉพาะราย
เตรียมไดเรคทอรี่ก่อน

# mkdir -p /sys1/sysb/etc/ddns
# ln -sf /sys1/sysb/etc/ddns /etc

สคริปต์ดูเลขไอพีจากเราเตอร์

ตั้งชื่อว่า d.router-getip เอาไว้ใน /usr/local/sbin
โดยถ้ามีอาร์กิวเมนต์ต่อท้ายว่า "RESET" จะสั่งให้เราเตอร์เริ่มการทำงานใหม่ สำหรับใช้ในกรณีที่เราเตอร์ฝั่งเราหรือฝั่งไอเอสพีเกิดอาการแฮงก์
(ต้องปรับสคริปต์ตามเราเตอร์ที่ใช้นะครับ)
# vi /usr/local/sbin/d.router-getip

#!/bin/bash
USER="ROUTER_ADMIN"
PASSWORD="ROUTER_PASSWORD"
IPAB="58.9"    #OUR FIRST 2 DIGIT OF IP NUMBER
ROUTERNAME="192.168.5.1"

if [ "$1" == "RESET" ]; then
    ADDRESS="http://$ROUTERNAME/rebootinfo.cgi"
    wget -o /dev/null -O - \
      --http-user="$USER" \
      --http-passwd="$PASSWORD" \
      "$ADDRESS"
    echo "RESET ROUTER"
else
    ADDRESS="http://$ROUTERNAME/wancfg.cmd?action=view"
    IP_ADDR=`wget -o /dev/null -O - \
      --http-user="$USER" \
      --http-passwd="$PASSWORD" \
      "$ADDRESS" \
      | grep $IPAB \
      | cut -d ">" -f 2 \
      | cut -d "<" -f 1`
    echo $IP_ADDR
fi

เปลี่ยนสถานะให้ root ดูได้เท่านั้น เพราะมีรหัสผ่านอยู่ในสคริปต์
# chmod 700 /usr/local/sbin/d.router-getip

สคริปต์ cron ที่จะตรวจไอพีมาเก็บไว้เป็นระยะ ทุก 5 นาที

ตั้งชื่อว่า d.router-cron-checkip เอาไว้ใน /usr/local/sbin
การทำงานคือ ถ้าอ่านค่าไอพีจากเราเตอร์ไม่ได้ (อาจเกิดจากเราเตอร์ของเราหรือของไอเอสพีแฮงก์) จะสั่งรีเซ็ตเราเตอร์ใหม่
# vi /usr/local/bin/d.router-cron-checkip

#!/bin/bash
DATA="/etc/ddns/router-ip"
OLD_IP=`cat $DATA`
CUR_IP=`/usr/local/sbin/d.router-getip`

#SOLVE ROUTER (OR ISP?) IS HUNG
if [ ! $CUR_IP ]; then
    /usr/local/sbin/d.router-checkip RESET
fi
#TEST BETWEEN SAVED-IP AND NEW-READ-IP
if [ "$OLD_IP" != "$CUR_IP" ]; then
    echo $CUR_IP > $DATA    #SAVE NEW IP
    /usr/local/sbin/d.router-reconnect
fi

เปลี่ยนสถานะให้รันได้
# chmod 700 /usr/local/bin/d.router-cron-checkip

ตั้ง cron ทุก 5 นาที (ถ้าคุณภาพสายไม่ดี อาจตั้งค่าให้บ่อยขึ้นก็ได้ครับ)
# crontab -e

...
#CHECK ROUTER IP ADDRESS EVERY 5 MIN
*/5 *   * * *   /usr/local/bin/d.router-cron-checkip
...

ในครั้งแรก ที่ยังไม่มีไฟล์ router-ip ต้องสร้างก่อน เพียงครั้งเดียว
# d.router-getip > /etc/ddns/router-ip

สคริปต์ cron ที่จะตรวจไอพีซ้ำ จาก dns ภายนอก ทุกครึ่งชั่วโมง

สร้างไฟล์ข้อมูลโดเมนเอาไว้ที่ /etc/ddns (เลียนแบบการวางไฟล์จาก apache2)
โดยเราจะเขียนไฟล์คอนฟิกเอาไว้ใน /etc/ddns/ddns-available และเมื่อเสร็จแล้วก็โยงลิงก์เข้าไปที่ /etc/ddns/ddns-enabled
(วิธีนี้ทำให้ปรับแต่งไฟล์ง่าย อันไหนที่ทดสอบแล้วว่ายังไม่เรียบร้อย ก็แค่ลบลิงก์ไป)
สคริปต์จะเข้าไปอ่านไฟล์คอนฟิกจากในไดเรกทอรี่ /etc/ddns/ddns-enabled
# mkdir -p /etc/ddns/{ddns-available,ddns-enabled}

สมมุติว่าเราจดทะเบียนไว้ที่ zoneedit.com และ everydns.net ไว้ 2 โดเมน
คือ example.com และ example.org
ตัวอย่างไฟล์คอนฟิก จะเป็นดังนี้
# vi /etc/ddns/ddns-available/zoneedit

#ZONEEDIT DATA
USER="ZONEEDIT-USER"
PASSWORD="ZONEEDIT-PASSWORD"
#UPDATE_DOMAIN="DOMAIN:NAME-SERVER, ... "       #NO SPACE IN FIELD
UPDATE_DOMAIN="
example.com:ns13.zoneedit.com
example.org:ns2.zoneedit.com"

# vi /etc/ddns/ddns-available/everydns

#EVERYDNS DATA
USER="EVERYDNS-USER"
PASSWORD="EVERYDNS-PASSWORD"
#UPDATE_DOMAIN="DOMAIN:NAME-SERVER, ... "       #NO SPACE IN FIELD
UPDATE_DOMAIN="
example.com:ns2.everydns.net
example.org:ns2.everydns.net"

ต้องระวัง พยายามอย่าพิมพ์ผิด เพราะข้อมูลในนี้คือตัวแปรในสคริปต์โดยตรง ไม่มีการตรวจสอบความถูกต้องจากสคริปต์อีกแล้ว

โยงลิงก์ เพื่อเปิดใช้งาน
# ln -sf /etc/ddns/ddns-available/* /etc/ddns/ddns-enabled

ต่อไปสร้างสคริปต์ชื่อ d.router-cron-checkddns เอาไว้ที่ /usr/local/sbin เพื่อตรวจสอบไอพีของเราจากผู้ให้บริการ
# vi /usr/local/sbin/d.router-cron-checkddns

#!/bin/bash
# CRON SCRIPT FOR TEST IP FROM PROVIDER.
# KNOWN PROVIDER:
#   zoneedit.com 
#   everydns.net
#
# PUT PROVIDERS DATA FILE IN /etc/ddns/ddns-available .
# MAKE LINK TO /etc/ddns/ddns-enabled TO ENABLE.

#DEFAULT DIRECTORY CONTAIN DDNS PROVIDERS DATA
DIR="/etc/ddns/ddns-enabled"

# GLOBAL VAR
CUR_IP=`/usr/local/sbin/d.router-getip`
UPDATE_SCRIPT="/usr/local/sbin/d.router-updatezone"

for PROVIDER in `ls -1 $DIR`; do
    #GET VARIABLE: $USER,$PASSWORD,$UPDATE_DOMAIN
    . $DIR/$PROVIDER
    for i in $UPDATE_DOMAIN; do
        DOMAIN=`echo $i | cut -d: -f1`
        NAME_SERVER=`echo $i | cut -d: -f2`
        TEST_IP=`nslookup $DOMAIN $NAME_SERVER | grep -v "#" | grep Address | cut -d\  -f2`
        if [ "$TEST_IP" != "$CUR_IP" ]; then
            $UPDATE_SCRIPT $PROVIDER
        fi
    done
done

ทำให้รันได้
# chmod 700 /usr/local/sbin/d.router-cron-checkddns

ตั้ง cron ทุก 30 นาที
# crontab -e

...
#CHECK DOMAIN IP ADDRESS EVERY 30 MIN
*/30 *   * * *   /usr/local/sbin/d.router-cron-checkddns
...
สคริปต์อัปเดต

เป็นการสั่งอัปเดต โดยนำค่าจากข้อมูลในไฟล์คอนฟิกข้างบน คือใน /etc/ddns/ddns-enabled มาอัปเดต ชื่อไฟล์คือค่าอาร์กิวเมนต์ของสคริปต์ ยกเว้นถ้าใส่ -a จะหมายถึง all คืออัปเดตทุก ๆ ผู้ให้บริการ

การทำงานของสคริปต์จะแบ่งเป็น 2 จังหวะ คือในครั้งแรกจะอัปเดตรวดเดียวกับทุกโดเมน เมื่อเสร็จเรียบร้อยแล้วจะย้อนกลับมาตรวจสอบอีกครั้งนึงว่า ในแต่ละโดเมนรายงานค่าไอพีถูกต้องหรือไม่ ถ้าไม่ถูกก็จะเว้นไป 5 วินาที แล้วเชื่อมต่อใหม่เป็นจำนวน 10 รอบการทำงาน

สร้างสคริปต์ชื่อ d.router-updatezone เอาไว้ที่ /usr/local/sbin ในการอัปเดตเมื่อไอพีเปลี่ยน
# vi /usr/local/sbin/d.router-updatezone

#!/bin/bash 
# SCRIPT FOR UPDATE DNS RECORD.
# KNOWN PROVIDER:
#   zoneedit.com 
#   everydns.net
#
# PUT PROVIDERS DATA FILE IN /etc/ddns/ddns-available .
# MAKE LINK TO /etc/ddns/ddns-enabled TO ENABLE.
# DATA FILE FORMAT:
#   USER="user"
#   PASSWORD="password"
#   UPDATE_DOMAIN="
#   example.com:ns1.zoneedit.com
#   example.com:ns2.zoneedit.com"

if [ ! $1 ]; then
    PROG=`basename $0`
    echo "Usage: $PROG [-a=all | ddns_provider_file]"
    exit
fi

#DEFAULT DIRECTORY CONTAIN DDNS PROVIDERS DATA
DIR="/etc/ddns/ddns-enabled"
if [ "$1" == "-a" ]; then
    DDNS_ENABLED=`ls -1 $DIR`
else
    DDNS_ENABLED=$1
fi

# GLOBAL VAR
IP_ADDR=`/usr/local/sbin/d.router-getip`

# FIRST FAST UPDATE
fastupdate() {
    ADDRESS=$1
    DOMAIN=$2
    USER=$3
    PASSWORD=$4
    echo "$DOMAIN fast update ..."
    wget -O - --http-user=$USER --http-passwd=$PASSWORD "$ADDRESS"
}

# SECOND RECHECK & UPDATE
recheck() {
    ADDRESS=$1
    DOMAIN=$2
    NAME_SERVER=$3
    USER=$4
    PASSWORD=$5
    echo "$DOMAIN recheck with $NAME_SERVER ..."
    TEST_IP=`nslookup $DOMAIN $NAME_SERVER | grep -v "#" | grep Address | cut -d \  -f 2`
    echo "REAL_IP=$IP_ADDR , TEST_IP=$TEST_IP" 
    I=0
    LOOP=10
    while [ $I -lt $LOOP ] && [ "$IP_ADDR" != "$TEST_IP" ] ; do
        wget -O - --http-user=$USER --http-passwd=$PASSWORD $ADDRESS
        sleep 5
        echo "ROUND=$I , NAME_SERVER=$NAME_SERVER , REAL_IP=$IP_ADDR , TEST_IP=$TEST_IP" 
        TEST_IP=`nslookup $DOMAIN $NAME_SERVER | grep -v "#" | grep Address | cut -d \  -f 2`
        I=$[$I+1]
    done
}

# BEGIN MAIN
#1.FAST UPDATE
for PROVIDER in $DDNS_ENABLED; do
    #GET VARIRABLE $USER,$PASSWORD,$UPDATE_DOMAIN
    . $DIR/$PROVIDER
    for i in $UPDATE_DOMAIN; do
        DOMAIN=`echo $i | cut -d: -f1`
        case $PROVIDER in
        zoneedit)
            ADDRESS="http://dynamic.zoneedit.com/auth/dynamic.html?host=$DOMAIN"
            ;;
        everydns)
            ADDRESS="http://dyn.everydns.net/index.php?ver=0.1&domain=$DOMAIN"
            ;;
        esac
        fastupdate $ADDRESS $DOMAIN $USER $PASSWORD
    done
done

#WAIT 30 SECONDS BEFORE RECHECK
sleep 30

#2.RECHECK
for PROVIDER in $DDNS_ENABLED; do
    #GET VARIABLE $USER,$PASSWORD,$UPDATE_DOMAIN
    . $DIR/$PROVIDER
    for i in $UPDATE_DOMAIN; do
        DOMAIN=`echo $i | cut -d: -f1`
        NAME_SERVER=`echo $i | cut -d: -f2`
        case $PROVIDER in
        zoneedit)
            # NEW wget , USED WHEN dynamic.zoneedit.com IS DOWN OR THIS MACHINE STAY BEHIND FIREWALL
            ADDRESS="http://www.zoneedit.com/auth/dynamic.html?host=$DOMAIN&type=A&dnsto=$IP_ADDR"
            ;;
        everydns)
            # WITH IP, SUITABLE FOR MACHINE BEHIND FIREWALL
            ADDRESS="http://dyn.everydns.net/index.php?ver=0.1&ip=$IP_ADDR&domain=$DOMAIN"
            ;;
        esac
        recheck $ADDRESS $DOMAIN $NAME_SERVER $USER $PASSWORD
    done
done

เปลี่ยนสถานะให้รันได้
# chmod 700 /usr/local/sbin/d.router-updatezone

สคริปต์รวมในการบันทึกไอพีใหม่และสั่งอัปเดต

สคริปต์นี้แทบไม่มีอะไร เพียงแต่ไปเรียกใช้สคริปต์อัปเดตข้างบน เพียงแต่ใส่พารามิเตอร์เป็น -a (อัปเดตทุกผู้ให้บริการ) เท่านั้น
แต่ว่าต้องสร้างสคริปต์นี้ไว้ เพราะเราจะต้องเชื่อมโยงกับการอัปเดต bind9 ที่จะติดตั้งต่อไป

ตั้งชื่อว่า d.router-reconnect เอาไว้ใน /usr/local/sbin
# vi /usr/local/sbin/d.router-reconnect

#!/bin/bash
# UPDATE ALL ENABLED ZONE

#KILL OLD PROCESS
killall d.router-updatezone
#UPDATE ALL ZONE
/usr/local/sbin/d.router-updatezone -a

#-------ADDITIONAL BIND9 SCRIPT:------------------

เสร็จแล้ว