debian: ทำ 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 เป็นระยะ ซึ่งวิธีนี้ทำให้สิ้นเปลืองแบนด์วิธโดยไม่จำเป็น สู้เขียนสคริปต์เอาเองดีกว่า
    ขั้นตอนนี้ขอติดไว้ก่อนครับ เพราะจะพยายามแบ่งตามเซิร์ฟเวอร์ที่เราไปจดทะเบียน

เกร็ด

  • ddns แต่ละที่ จะมีความเร็วในการอัปเดตต่างกัน
    • zoneedit.com ใช้เวลาประมาณ 1 นาที
    • everydns.net ประมาณ 2-5 นาที

    รวมทั้งขั้นตอนการอัปเดตก็ต่างกัน

    • zoneedit.com ใช้ wget อย่างเดียว
    • ส่วน everydns.net ใช้สคริปต์ Perl
  • ข้อเสียของวิธีนี้คือ
    • ต้องเปิดเครื่องทิ้งไว้ตลอดเวลา
    • ต้องพึ่งพาการใช้ DDNS จากเมืองนอก เพราะเมืองไทยยังไม่มีบริการแบบนี้ เวลาที่มีปัญหาช่องสัญญาณเมืองนอกขัดข้อง การอัปเดตจะขัดข้องตามไปด้วย
    • ถ้าการใช้งานอินเตอร์เนตของเราเป็นแบบ adsl (Asynchronous Digital Subscriber Line) ความเร็วในการอัปโหลดจะต่ำกว่าความเร็วในการดาวน์โหลดพอสมควร จึงควรเพิ่มความเร็วให้สูงเข้าไว้ จะให้ผลดีกว่า
  • ส่วนข้อดีก็คือ เราสามารถทดลองได้อย่างที่เราต้องการ ไม่มีข้อจำกัดของโฮสติ้งมาเกี่ยว
  • ในข้อเขียนนี้ ไม่ได้อธิบายการป้องกันเซิร์ฟเวอร์ไว้ด้วย ดังนั้นเพื่อความปลอดภัย ควรป้องกันเซิร์ฟเวอร์เราไว้ก่อนหนึ่งชั้นด้วยแพกเกจ portsentry
    # aptitude install portsentry
    โดยเราจะใช้ค่าปริยายทั้งหมด โดยไม่ต้องปรับแต่งอะไรเลย
    ส่วนการป้องกันเซิร์ฟเวอร์ให้ดียิ่งขึ้น ควรศึกษาเพิ่มเติมด้วยครับ
  • รายชื่อ Free Dynamic DNS พร้อมรีวิว
Topic: 

สร้างสคริปต์ DDNS สำหรับโมเด็ม adsl ให้ zoneedit

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

สมมุติว่า

  • เซิร์ฟเวอร์เราชื่อ www.example.com
  • เราลงทะเบียน dynamic dns ไว้ในฃื่อ MYUSERNAME รหัสผ่าน MYPASSWORD
  • อินเทอร์เฟสที่เกิดจากโมเด็มคือ ppp0

สำหรับการต่อเน็ต 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.updatezoneedit
fi
...

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

#!/bin/bash
# SCRIPT FOR PPP TO UPDATE DNS RECORD AT zoneedit.com

# UPDATE FUNCTION
#usage: updatezonedit $USER $PASSWORD $IP_ADDR $DOMAIN $HOST 
#     : updatezonedit 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.updatezoneedit

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

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

สร้างสคริปต์ DDNS สำหรับโมเด็ม adsl ให้ everydns

สำหรับ everydns.net
แก้ไขไฟล์ 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.updateeverydns
fi
...

สร้างสคริปต์อัปเดตชื่อ /usr/sbin/d.updateeverydns ดังนี้
# vi /usr/sbin/d.updatezeverydns

#!/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

updatezone() {
    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 ..."
updatezone $USER $PASSWD $IP_ADDR $DOMAIN1 $HOST1
echo "Finished."

# chmod 0700 /usr/sbin/d.updateeverydns

ตามคำแนะนำของ 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.updateeverydns

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

debian: gmail + postfix

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

ปัญหาคือเมลเซิร์ฟเวอร์สาธารณะไม่ยอมรับจดหมายจากเครื่องที่มีไอพีไม่คงที่
จะแก้โดยให้ 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

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

อ้างอิง

สร้างสคริปต์ DDNS สำหรับเราเตอร์ ให้ zoneedit

กรณีเราเตอร์จะต่างจากกรณีโมเด็ม คือเราไม่สามารถทราบได้ว่าสายจะหลุดเมื่อใด ดังนั้นเราจึงต้องอาศัยการเช็คไอพีเป็นระยะ โดยอาศัยการทำงานของ 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/16

สมมุติว่า

  • ไอพีเราเตอร์ เป็น 192.168.5.1
  • ชื่อผู้ใช้เราเตอร์ชื่อ ADMIN รหัสผ่านเป็น ADMIN-PASSWORD
  • ไอพีของการ์ดแลน eth1 บนเซิร์ฟเวอร์ เป็น 192.168.5.3
  • ไอพีของการ์ดแลน eth0 บนเซิร์ฟเวอร์ เป็น 192.168.1.1

เราสามารถดูไอพีของเราเตอร์ที่เป็นสายนอกได้จากคำสั่ง 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
# 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.update-zoneedit
fi

# chmod 755 /usr/local/bin/d.router-cron-checkip

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

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

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

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

  • การฟอร์เวิร์ดไอพี
    # vi /etc/sysctl.conf
    ...
    net.ipv4.ip_forward = 1
    ...

    # echo 1 > /proc/sys/net/ipv4/ip_forward

  • การเพิ่มเกตเวย์ไปที่เราเตอร์
    # route add default gw 192.168.5.1
  • การทำตาราง nat ด้วย iptables
    # WAN_INT=eth1
    # iptables -t nat -D POSTROUTING -o $WAN_INT -j MASQUERADE > /dev/null
    # iptables -t nat -A POSTROUTING -o $WAN_INT -j MASQUERADE
  • อย่าลืมตั้งค่าการทำ NAT ที่เราเตอร์ให้ชี้ DMZ host มาที่ 192.168.5.3 ด้วย
  • คำสั่งในการดูเลขไอพี ตามตัวอย่างเป็น http://192.168.5.1/wancfg.cmd?action=view แต่เวลาใช้งานจริงต้องดูว่าเราเตอร์เราใช้คำสั่งเดียวกันนี้หรือไม่ ส่วนใหญ่ดูจาก Status Bar ด้านล่างของบราวเซอร์ เวลาที่เราเอาเมาส์ไปชี้ตรงลิงก์