ทำ Host ใช้เอง

เห็นมีการคุยกันที่ codenone เรื่องโฮสต์ไพธอนหายากมาก จึงขออนุญาตบันทึกการทำโฮสต์ไว้ใช้เองไว้ที่นี่แทน เพราะว่าเราใช้เดเบียน ;D เพื่อจะได้สามารถใช้งาน Python Ruby หรือแพกเกจที่ไม่ใช่แพกเกจท้องตลาดได้อย่างอิสระเสรี

ขั้นตอนคร่าว ๆ คือ

  1. คิดชื่อโดเมน และตรวจสอบว่าชื่อที่เราตั้งยังว่างอยู่หรือไม่
  2. จดทะเบียน dynamic domain name server
  3. จดทะเบียนชื่อโดเมน
  4. สร้างสคริปต์สำหรับเปลี่ยนไอพี เมื่อสายหลุด

เริ่มด้วย

  1. คิดชื่อโดเมน และตรวจสอบว่าชื่อที่เราตั้งยังว่างอยู่หรือไม่
    สามารถตรวจดูได้จากเว็บโฮสติ้งทั่วไป
  2. จดทะเบียน dynamic domain name server
    ที่ผมใช้อยู่คือ

    นอกจากสองที่นี้แล้ว ยังมีอีกมาก สามารถลองค้นจากกูเกิลได้ ด้วยคำว่า "free dynamic dns" ครับ

    เมื่อลงทะเบียนและใส่ชื่อโดเมนเราแล้ว เขาจะให้ชื่อ DNS เรามาสองชื่อ เช่นของ zoneedit จะเป็น ns1.zoneedit.com และ ns2.zoneedit.com (ชื่อ DNS จริงอาจแปลกไปจากตัวอย่าง) ซึ่งเราจะเอาชื่อทั้งสองชื่อนี้ ไปจดทะเบียนชื่อโดเมนต่อไป

  3. จดทะเบียนชื่อโดเมน
    ขั้นตอนนี้ก็สามารถจดได้ทั่วไปครับ โดยในขั้นตอนการจดทะเบียน เขาจะมีช่องให้ใส่ชื่อ DNS เราก็เอาชื่อที่ได้มาจากขั้นตอนก่อน ทั้งสองชื่อใส่ลงไปก็เรียบร้อย
  4. สร้างสคริปต์สำหรับเปลี่ยนไอพี เมื่อสายหลุด
    ขั้นตอนนี้ จริง ๆ แล้วมีสคริปต์ที่เขาทำกันเอาไว้อยู่แล้ว เช่น ddclient แต่ผมใช้ไม่เป็นครับ และเห็นว่าส่วนใหญ่เขาจะใช้วิธี ping ไปที่ DNS เป็นระยะ ซึ่งวิธีนี้ทำให้สิ้นเปลืองแบนด์วิธโดยไม่จำเป็น สู้เขียนสคริปต์เอาเองดีกว่า

ขั้นตอนนี้ สำหรับโมเด็ม adsl ในตัว ให้ดูในหัวข้อถัดไปครับ ส่วนกรณีที่ใช้เราเตอร์ ขอติดไว้เป็นครั้งหน้า

เกร็ด

กรณีใช้โมเด็ม ADSL

สมมุติว่า

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

  1. ตอนสายหลุด ระบบจะเรียกสคริปต์ /etc/ppp/ip-down.local
  2. ตอนที่เชื่อมต่อใหม่สำเร็จแล้ว ระบบจะเรียกใช้สคริปต์ /etc/ppp/ip-up.local

เราใช้แค่สคริปต์ /etc/ppp/ip-up.local ก็พอ โดยจะดักการเชื่อมต่อตรงจุดนี้ โดยเราจะสร้างสคริปต์ย่อยในการอัปเดตขึ้นมาอีกสคริปต์นึง เนื่องจากในการอัปเดต จำเป็นต้องมีชื่อผู้จดทะเบียนและรหัสผ่านอยู่ในสคริปต์ด้วย เราจึงต้องนำสคริปต์ไปไว้ในที่ปลอดภัย (ผมเอาไปใส่ใน /usr/sbin)

สำหรับ zoneedit.com

แก้ไขไฟล์ ip-up.local ดังนี้

# vi /etc/ppp/ip-up.local
...
if [ $PPP_IFACE == "ppp0" ]; then
    # REFRESH DNS
    # REFRESH IPTABLES
    # REFRESH SQUID
    # RECONNECT DDNS
    /usr/sbin/d.update-zoneedit
fi
...

สร้างสคริปต์อัปเดตชื่อ /usr/sbin/d.update-zoneedit ดังนี้

# vi /usr/sbin/d.update-zoneedit
#!/bin/bash
# SCRIPT FOR PPP TO UPDATE DNS RECORD AT zoneedit.com

# UPDATE FUNCTION
#usage: updatezoneedit $USER $PASSWORD $IP_ADDR $DOMAIN $HOST 
#     : updatezoneedit MYUSERNAME MYPASSWORD $PPP_IP example.com www.example.com

updatezoneedit() {
    USER=$1
    PASSWORD=$2
    IP_ADDR=$3
    DOMAIN=$4
    HOST=$5
    wget -O - --http-user=$USER --http-passwd=$PASSWD \
      "http://www.zoneedit.com/auth/dynamic.html?host=$DOMAIN&type=A&dnsto=$IP_ADDR"
}

# BEGIN MAIN PROGRAM
I_FACE=ppp0
USER="MYUSERNAME"
PASSWD="MYPASSWORD"
DOMAIN1="example.com"
HOST1="www.example.com"

IP_ADDR=`ifconfig $I_FACE | fgrep -i inet | cut -d : -f 2 | cut -d \  -f 1`

# FOR example.com
echo "Updating $DOMAIN1 ..."
updatezoneedit $USER $PASSWD $IP_ADDR $DOMAIN1 $HOST1
echo "Finished."

ทำให้รันได้

# chmod 0700 /usr/sbin/d.update-zoneedit

ครั้งแรกเราเรียกใช้ครั้งเดียว ที่เหลือระบบจะทำอัตโนมัติทุกครั้งที่สายหลุด หรือเปิดเครื่อง

# /usr/sbin/d.update-zoneedit

เสร็จแล้วครับ

สำหรับ everydns

แก้ไขไฟล์ ip-up.local ดังนี้

# vi /etc/ppp/ip-up.local
...
if [ $PPP_IFACE == "ppp0" ]; then
    # REFRESH DNS
    # REFRESH IPTABLES
    # REFRESH SQUID
    # RECONNECT DDNS
    /usr/sbin/d.update-everydns
fi
...

สร้างสคริปต์อัปเดตชื่อ /usr/sbin/d.update-everydns ดังนี้

# vi /usr/sbin/d.update-everydns
#!/bin/bash
# SCRIPT FOR PPP TO UPDATE DNS RECORD AT everydns.net

# PREREQUIST:
# 0.REGISTER USERNAME & PASSWORD AT www.everydns.net
#   - ADD DYNAMIC DNS example.com
#   - ADD DYNAMIC DNS www.example.com
# 1.DOWNLOAD FILE http://www.everydns.net/eDNS.pl 
#   PUT IN /usr/local/bin
# 2.INSTALL ncftp
#   # aptitude install ncftp
# 3.INSTALL PERL MIME::Base64
#   # perl -MCPAN -e 'install MIME::Base64'

# UPDATE FUNCTION
#usage: updateeverydns $USER $PASSWORD $IP_ADDR $DOMAIN $HOST 
#     : updateeverydns MYUSERNAME MYPASSWORD $PPP_IP example.com www.example.com

updateeverydns() {
    USER=$1
    PASSWORD=$2
    IP_ADDR=$3
    DOMAIN=$4
    HOST=$5
    eDNS.pl -u $USER -p $PASSWORD -ip $IP_ADDR -d $DOMAIN
    eDNS.pl -u $USER -p $PASSWORD -ip $IP_ADDR -d $HOST
}

# BEGIN MAIN PROGRAM
I_FACE=ppp0
USER="MYUSERNAME"
PASSWD="MYPASSWORD"
DOMAIN1="example.com"
HOST1="www.example.com"

IP_ADDR=`ifconfig $I_FACE | fgrep -i inet | cut -d : -f 2 | cut -d \  -f 1`

# FOR example.com
echo "Updating $DOMAIN1 ..."
updateeverydns $USER $PASSWD $IP_ADDR $DOMAIN1 $HOST1
echo "Finished."

ทำให้รันได้

# chmod 0700 /usr/sbin/d.update-everydns

ตามคำแนะนำของ everydns คือจะต้องใช้สคริปต์ของเขา ซึ่งสคริปต์นั้นเขียนด้วย perl มีการเรียกใช้ ncftp จึงต้อง...

ดาวน์โหลดสคริปต์มาก่อน และนำไปเก็บไว้ที่ /usr/local/bin

# wget http://www.everydns.net/eDNS.pl
# chmod 755 eDNS.pl
# mv eDNS.pl /usr/local/bin

ในสคริปต์จะต้องเรียกใช้ ncftp จึงต้องติดตั้ง ncftp ก่อน

# aptitude install ncftp

ติดตั้ง perl MIME::base64 ตามคำแนะนำ

# perl -MCPAN -e 'install MIME::Base64'

ครั้งแรกเราเรียกใช้ครั้งเดียว ที่เหลือระบบจะทำอัตโนมัติทุกครั้งที่สายหลุด หรือเปิดเครื่อง

# /usr/sbin/d.update-everydns

เสร็จแล้วครับ

(ท่านใดเอาไปใช้งาน ถ้าไม่ผ่านรบกวนแจ้งด้วยนะครับ เพราะตัดทอนจากโปรแกรมที่ใช้งานอยู่ บางทีอาจตรวจทานหลุด และไม่มีฮาร์ดแวร์สำหรับทดสอบแล้ว)

สร้างสคริปต์ DDNS สำหรับเราเตอร์กับงานทำโฮสต์ใช้เอง

จากครั้งก่อนเรื่อง ทำโฮสต์ใช้เอง ยังขาดเนื้อหาว่า ถ้าอุปกรณ์ของเราเป็นเราเตอร์ เราจะมีวิธีในการอัปเดตข้อมูล DNS อย่างไร

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

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

Internet  ->  Router  ->  Server:eth1
            192.168.5.1   192.168.5.3
                                |
                                v
                           Server:eth0  -> Internal Network
                           192.168.1.1      192.168.1.0/24

สมมุติว่า

เราสามารถดูไอพีของเราเตอร์ที่เป็นสายนอกได้จากคำสั่ง wget

ตัวอย่างของผมใช้คำสั่ง

# wget -o /dev/null -O - --http-user="ADMIN" --http-passwd="ADMIN-PASSWORD" \
"http://192.168.5.1/wancfg.cmd?action=view"

จะได้ผลลัพธ์ออกมาเต็มไปหมด แต่ในนั้นจะมีหมายเลขไอพีเราอยู่ด้วย

ต่อไปเราจะกรองข้อมูลที่เราไม่ต้องการทิ้ง ให้เหลือแต่เลขไอพี

ตอนนี้เราใช้เทคนิคคือ แม้ว่าไอพีเราจะเปลี่ยนทุกครั้งที่ต่อสายใหม่ แต่ตัวที่ไม่เปลี่ยนคือตัวเลขสองหลักหน้า ดังนั้นเราจะเอาตัวเลขสองหลักหน้ามาเป็นตัวกรอง

เช่นของผมเป็น 58.9.XXX.XXX เราจะเพิ่มการกรองด้วยคำสั่ง grep 58.9

# wget -o /dev/null -O - --http-user="ADMIN" --http-passwd="ADMIN-PASSWORD" \
"http://192.168.5.1/wancfg.cmd?action=view" \
| grep 58.9

ได้ผลลัพธ์เป็น

      <td>58.9.XXX.XXX</td>

เพิ่มการกรองอีกชั้นนึงให้ตัดแท็ก html:td ทิ้ง คำสั่งกลายเป็น

# wget -o /dev/null -O - --http-user="ADMIN" --http-passwd="ADMIN-PASSWORD" \
"http://192.168.5.1/wancfg.cmd?action=view" \
| grep 58.9 | cut -d ">" -f 2 | cut -d "<" -f 1

ได้แล้ว

58.9.XXX.XXX

ถึงตอนนี้เพื่อให้ใช้งานสะดวก และเป็นการปกปิดชื่อและรหัสผ่าน เราจะทำเป็นสคริปต์ เก็บไว้ที่ /usr/sbin ผมตั้งชื่อว่า d.router-getip

# vi /usr/sbin/d.router-getip
#!/bin/bash
CMD="http://192.168.5.1/wancfg.cmd?action=view"
USER="ADMIN"
PASSWORD="ADMIN-PASSWORD"
FIRSTTWODIGIT="58.9"
IP_ADDR=`wget -o /dev/null -O - --http-user=$USER \
  --http-passwd=$PASSWORD $CMD \
  | grep $FIRSTTWODIGIT | cut -d ">" -f 2 | cut -d "<" -f 1` \
  >> /dev/null
echo $IP_ADDR
# chmod 0700 /usr/sbin/d.router-getip

เวลาเรียกใช้ก็สั่ง d.router-getip ก็จะได้ไอพีของ WAN ออกมา

ต่อไปเราจะสร้างสคริปต์ให้มีการตรวจสอบว่าไอพีตรงกับค่าปัจจุบันหรือไม่ ถ้าตรงก็ไม่ทำอะไร แต่ถ้าไม่ตรงก็จะให้ไปเรียกสคริปต์ให้ไปอัปเดต DDNS และเก็บค่าไอพีใหม่ ผมตั้งชื่อไฟล์ว่า d.router-cron-checkip ซึ่งผมเก็บไว้ที่ /usr/local/bin

กรณี zoneedit
# vi /usr/local/bin/d.router-cron-checkip
#!/bin/bash
OLD_IP=`cat /root/router-ip`
CUR_IP=`/usr/sbin/d.router-getip`
if [ $OLD_IP != $CUR_IP ]; then
    echo $CUR_IP > /root/router-ip
    /usr/sbin/d.router-update-zoneedit
fi
# chmod 755 /usr/local/bin/d.router-cron-checkip

สร้างไฟล์อัปเดตเอาไว้ที่ /usr/sbin

# vi /usr/sbin/d.router-update-zoneedit
#!/bin/bash
# SCRIPT FOR ROUTER TO UPDATE DNS RECORD AT zoneedit.com

updatezoneedit() {
    USER=$1
    PASSWORD=$2
    IP_ADDR=$3
    DOMAIN=$4
    HOST=$5
    # NEW wget 48-10-8 USED WHEN dynamic.zoneedit.com IS DOWN
    wget -O - --http-user=$USER --http-passwd=$PASSWORD \
"http://www.zoneedit.com/auth/dynamic.html?host=$DOMAIN&type=A&dnsto=$IP_ADDR"
}

USER="MYUSERNAME"
PASSWD="MYPASSWORD"
DOMAIN1="example.com"
HOST1="www.example.com"
IP_ADDR=`/usr/sbin/d.router-getip`
updatezoneedit $USER $PASSWD $IP_ADDR $DOMAIN1 $HOST1

เสร็จสำหรับ zoneedit

กรณีeverydns
# vi /usr/local/bin/d.router-cron-checkip
#!/bin/bash
OLD_IP=`cat /root/router-ip`
CUR_IP=`/usr/sbin/d.router-getip`
if [ $OLD_IP != $CUR_IP ]; then
    echo $CUR_IP > /root/router-ip
    /usr/sbin/d.router-update-everydns
fi
# chmod 755 /usr/local/bin/d.router-cron-checkip

สร้างไฟล์อัปเดตเอาไว้ที่ /usr/sbin

# vi /usr/sbin/d.router-update-everydns
#!/bin/bash
# SCRIPT FOR ROUTER TO UPDATE DNS RECORD AT everydns.net

updateeverydns() {
    USER=$1
    PASSWORD=$2
    IP_ADDR=$3
    DOMAIN=$4
    HOST=$5
    eDNS.pl -u $USER -p $PASSWORD -ip $IP_ADDR -d $DOMAIN
    eDNS.pl -u $USER -p $PASSWORD -ip $IP_ADDR -d $HOST
}

USER="MYUSERNAME"
PASSWD="MYPASSWORD"
DOMAIN1="example.com"
HOST1="www.example.com"
IP_ADDR=`/usr/sbin/d.router-getip`
updateeverydns $USER $PASSWD $IP_ADDR $DOMAIN1 $HOST1

เสร็จสำหรับ everydns

งานต่อไปคือการตั้งให้ crontab ตรวจสอบค่าไอพีทุก 5 นาที

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

เสร็จแล้วครับ

เกร็ด

จากตัวอย่าง ผมละเลยที่จะกล่าวถึงการทำตาราง nat เพราะความรู้ไม่พอที่จะอธิบาย จึงขอลงวิธีการไว้ตอนท้ายเพื่อให้สามารถศึกษาต่อได้เองดังนี้ครับ

แก้ปัญหาการส่งเมล โดยใช้ gmail

การแก้ปัญหาการส่งเมล สำหรับโฮสต์ที่ใช้ dynamic ip โดยใช้ gmail

ปัญหาคือเมลเซิร์ฟเวอร์สาธารณะไม่ยอมรับจดหมายจากเครื่องที่มีไอพีไม่คงที่
จะแก้โดยให้ gmail เป็นผู้ส่งจดหมายให้
สมมุติว่าเราได้ติดตั้ง postfix ไว้แล้ว

ขั้นตอนสำหรับ gmail

  1. สมัครสมาชิก gmail
  2. ไปที่ การตั้งค่า(settings) -> การส่งต่อและ POP/IMAP (Forwarding and POP/IMAP) ->
    การเข้าถึงแบบ IMAP: (IMAP Access:) เลือก ใช้งาน IMAP (Enable IMAP)

เสร็จแล้ว

ขั้นตอนของ postfix

  1. เปิดใช้ relay โดยใช้คำสั่งชุดนี้
    # postconf -e 'relayhost = smtp.gmail.com'
    # postconf -e 'smtp_sasl_auth_enable = yes'
    # postconf -e 'smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd'
    # postconf -e 'smtp_sasl_security_options ='
  2. ตั้งค่ารหัสผ่าน ใช้ชื่อและรหัสผ่านชุดเดียวกับตอนที่สมัคร gmail โดยใช้คำสั่งชุดนี้
    # echo "smtp.gmail.com USER@gmail.com:PASSWORD" >> /etc/postfix/sasl_passwd
    # chown root:root /etc/postfix/sasl_passwd
    # chmod 600 /etc/postfix/sasl_passwd
    # postmap /etc/postfix/sasl_passwd
  3. แล้วก็เริ่ม postfix ใหม่
    # /etc/init.d/postfix restart

เสร็จแล้ว
หลังจากนี้เวลาเซิร์ฟเวอร์เราส่งเมลออก เขาจะใช้กูเกิลในการส่งแทน

อ้างอิง