Author: chatchai.j

  • เปลี่ยน uptime timestamp ในคำสั่ง dmesg ให้เป็น date/time timestamp

    เคยใหมครับ เมื่อใช้คำสั่ง dmesg เพื่อที่จะดู kernel message แล้วสงสัยว่า เวลาที่แสดงในเครื่องหมาย square bracket ที่อยู่ด้านหน้าของ message น่ะมันเป็นเวลาเท่าไหร่กันแน่?

    อาจจะไม่บ่อยนัก แต่ผมก็เคยมีปัญหานั้น

    ถ้าใช้คำสั่ง dmesg บน command line เราอาจจะเห็น message ประมาณนี้

     [   10.738140] udev[228]: starting version 164
     [   12.840798] ACPI: PCI Interrupt Link [LNKD] enabled at IRQ 9
     [   12.842168] PCI: setting IRQ 9 as level-triggered
     [   12.843002] pci 0000:00:04.0: PCI INT A -> Link[LNKD] -> GSI 9 (level, low) -> IRQ 9
     [   12.856454] input: PC Speaker as /devices/platform/pcspkr/input/input2
     [   12.911121] vboxguest: major 0, IRQ 9, I/O port d020, MMIO at 00000000f0400000 (size 0x400000)
     [   12.912531] vboxguest: Successfully loaded version 3.2.10_OSE (interface 0x00010004)
     [   13.165633] piix4_smbus 0000:00:07.0: SMBus base address uninitialized - upgrade BIOS or use force_addr=0xaddr
     [   13.233473] parport_pc 00:05: reported by Plug and Play ACPI
     [   13.314410] ACPI: AC Adapter [AC] (on-line)
     [   13.318154] input: Power Button as /devices/LNXSYSTM:00/LNXPWRBN:00/input/input3
     [   13.345820] ACPI: Power Button [PWRF]
     [   13.376871] input: Sleep Button as /devices/LNXSYSTM:00/LNXSLPBN:00/input/input4
     [   13.420217] ACPI: Sleep Button [SLPF]
     [   13.455000] input: ImExPS/2 Generic Explorer Mouse as /devices/platform/i8042/serio1/input/input5
     [   15.017345] Intel ICH 0000:00:05.0: PCI INT A -> Link[LNKA] -> GSI 5 (level, low) -> IRQ 5
     [   15.019308] Intel ICH 0000:00:05.0: setting latency timer to 64
     [   15.344130] intel8x0_measure_ac97_clock: measured 54439 usecs (10177 samples)
     [   15.346177] intel8x0: measured clock 186943 rejected
     [   15.704139] intel8x0_measure_ac97_clock: measured 55930 usecs (10470 samples)
     [   15.705569] intel8x0: measured clock 187198 rejected
     [   16.064125] intel8x0_measure_ac97_clock: measured 55919 usecs (10451 samples)
     [   16.065415] intel8x0: measured clock 186895 rejected
     [   16.066703] intel8x0: clocking to 48000
     [   17.234971] EXT3 FS on dm-0, internal journal
     [   17.967548] loop: module loaded
     [   23.754984] Adding 8388600k swap on /dev/mapper/lv-nilanont--swap.  Priority:-1 extents:1 across:8388600k
     [   30.580221] kjournald starting.  Commit interval 5 seconds
     [   30.581590] EXT3 FS on dm-2, internal journal
     [   30.582599] EXT3-fs: mounted filesystem with ordered data mode.
     [   31.094231] FAT: utf8 is not a recommended IO charset for FAT filesystems, filesystem will be case sensitive!
     [   35.777014] e1000: eth0 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
     [   39.640499] fuse init (API version 7.13)
     [   40.373844] binfmt_misc: Unknown symbol bprm_change_interp
     [   40.469771] binfmt_misc: Unknown symbol bprm_change_interp
     [   41.587335] vboxsf: Successfully loaded version 3.2.10_OSE (interface 0x00010004)
     [   52.668229] lp: driver loaded but no devices found
     [   52.742090] ppdev: user-space parallel port driver
     [   64.384194] device eth0 entered promiscuous mode
     [   66.379973] device eth0 left promiscuous mode
     [   73.139965] tun: Universal TUN/TAP device driver, 1.6
     [   73.141787] tun: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>

    ตัวเลขที่อยู่ภายใน [ ] เช่น “[   73.141787]” ในบรรทัดสุดท้าย 73.141787 หมายความว่า ข้อความของ kernel ในบรรทัดนั้น

    tun: (C) 1999-2004 ...

    พิมพ์ออกมาหลังจากเครื่องเริ่มต้นทำงานไปแล้วประมาณ 17 วินาทีกับอีกนิดหน่อย 141787 ที่อยู่ข้างหลัง จะแสดงเวลาที่เป็นหน่วยย่อยในระดับ micro-second

    ซึ่งการแสดงผลแบบนี้ จะไม่มีปัญหาซักเท่าไหร่ สำหรับเครื่องที่เพิ่งเปิดขึ้นมาใหม่ๆ เช่นเครื่อง desktop หรือ notebook ทั่วไป แต่สำหรับเครื่องที่ทำงานเป็น Server ซึ่งเปิดทิ้งเอาไว้เป็นชั่วนาตาปี, โขง, ชี, มูล, ปิง, วัง, ยม, น่าน นั้น บางทีเวลาเราใช้คำสั่ง dmesg ออกมา อาจจะเห็นเป็น message แบบนี้

     ...
     [201089.204812] br0: topology change detected, propagating
     [201089.204815] br0: port 1(eth0) entering forwarding state
     [201089.204838] br0: port 1(eth0) entering forwarding state
     [237531.522878] br0: port 1(eth0) entering forwarding state
     [237531.671857] r8169 0000:02:00.0: eth0: link down
     [237531.671888] r8169 0000:02:00.0: eth0: link down
     ...

    จะเห็นว่าบรรทัด “br0: port 1(eth0) enter forwarding state” ในสองบรรทัดแรก กับบรรทัดที่สาม จะเกิดห่างกันพอสมควร ระยะเวลาห่างกันเท่าไหร่ เป็นเวลากี่วินาที ก็เอาตัวเลขทั้งสองตัวมาหักลบกันได้ ยุ่งยากนิดหน่อย แต่ก็ไม่มากจนเกินไป

    แต่ปัญหาที่เกิดขึ้นก็คือ หลายครั้ง ผมอยากจะรู้ว่า “มันเกิดขึ้นเมื่อไหร่?” มากกว่า ที่อยากจะรู้ว่า “มันเกิดขึ้นหลังจากครั้งที่แล้วนานเท่าไหร่?”

    อืม แล้วถ้าเป็นอย่างนั้นผมจะหาคำตอบได้ยังไง?
    เนื่องจากเวลาที่คำสั่ง dmesg แสดงออกมา เป็นเวลานับเป็นจำนวนวินาที นับตั้งแต่เครื่องเริ่มต้นทำงาน เพราะฉะนั้น ถ้าเรา “รู้” ว่าเราเริ่มเปิดเครื่องเมื่อไหร่ เราก็สามารถ ใช้เวลานั้นเป็นเวลาตั้งต้น แล้วบวกเวลาด้วยจำนวนวินาทีที่ผ่านไป แล้วค่อยแปลงเวลานั่นกลับมาเป็นวันเดือนปีอีกครั้ง เราก็จะรู้เวลาที่เกิดเหตุการณ์นั้นขึ้นใช่ใหมครับ

    ฟังดูไม่ยาก … รึเปล่า?

    เวลาที่เครื่องเริ่มต้นทำงานเราจะเอามันมาจากใหนได้บ้างล่ะนี่?

    ถ้าเครื่องของเราเพิ่ง reboot มาเมื่อไม่นานเท่าไหร่ เราอาจจะใช้คำสั่ง last ในการหาเวลา boot ของเครื่องได้โดยประมาณแบบนี้

     $ last | grep reboot
     reboot   system boot  3.2.0-4-amd64    Tue Mar 19 18:14 - 02:25 (6+08:10)

    อืม … เครื่องของผมเพิ่ง reboot ไปเมื่อวันที่ 19 มีค. เวลา 18:14 และ เวลาขณะที่เขียนบันทึกฉบับนี้ เวลาผ่านไปแล้ว 6 วัน 8 ชม. กับอีก 10 นาที … แต่ว่า วิธีการนี้จะมีปัญหากับเครื่อง boot มาแล้วตั้งแต่เดือนที่แล้ว หรือ นานกว่านั้น เพราะ /var/log/wtmp จะเก็บข้อมูลเฉพาะเดือนนี้ และ โดยทั่วไปแล้วจะมีข้อมูลเก็บย้อนหลังอีกแค่ 1 เดือนเท่านั้น (สำหรับ server ที่ผมดูแลอยู่ ข้อมูลของ wtmp จะระบุใน logrotate config ให้เก็บย้อนหลังไปอย่างน้อย 12 เดือน) … ไม่ว่าอย่างไร วิธีการนี้จะมีปัญหาถ้าต้องการที่จะได้ข้อมูลที่ถูกต้องแน่นอนทุกครั้ง

    งั้นมีวิธีใหนอีกบ้าง? คำสั่ง uptime ล่ะ

     $ uptime
     02:31:56 up 6 days, 8:17, 23 users, load average: 1.00, 0.98, 0.95

    แต่ … เอ วิธีการนี้เราจะได้มาแค่ ระยะเวลาที่เครื่องทำงานอยู่เท่านั้นนี่นา ไม่ได้บอกว่าเครื่อง ‘เริ่ม’ ทำงานเมื่อไหร่ … ใช่ใหม?

    ใช่แล้วครับ! แต่เรามีคำสั่งที่จะช่วยอีก 1 คำสั่งคือคำสั่ง date

     $ date;uptime
     Tue Mar 26 02:33:05 ICT 2013
     02:33:05 up 6 days, 8:18, 23 users, load average: 0.85, 0.95, 0.94

    เอาล่ะ คราวนี้ เราได้เวลา ‘ขณะนี้’ ของเครื่องออกมาแล้ว จากคำสั่ง date และเราได้ ‘ระยะเวลาที่เครื่องทำงาน’ มาจากคำสั่ง uptime เพราะฉะนั้น ถ้าผมนับย้อนหลังจากเวลา ‘ขณะนี้’ ไปเท่ากับเวลา ‘uptime’ ผมก็จะได้เวลาที่เครืองเริ่มต้นทำงานขึ้นมา … ใช่ใหมครับ

    start_time = "Tue Mar 26 02:33:05 ICT 2013" - "6 days, 8:18"

    เอ่อ … จะง่ายไปใหมเนี่ย เอามันมาลบได้อย่างนั้นน่ะ? ถ้าทำอย่างได้จริง ชีวิตของ sysadmin ก็คงง่ายขึ้นครับ แต่ชีวิตจริงของ admin มันไม่ง่ายดายขนาดนั้น

    แต่ถ้าจะให้ผมแปลง string ของวันที่กลับมาเป็นตัวเลข และแปลง string ของระยะเวลา กลับมาเป็นตัวเลข โดยใช้ shell script เองก็คงไม่ไหวเหมือนกันแหละครับ 🙂

    บน unix เรามี tools ซึ่งช่วยในการจัดการเรื่อง วัน/เวลาอยู่แล้ว คำสั่งนั้นก็คือ คำสั่ง date เพื่อนเก่าของเราน่ะเอง

    ถ้าเราใช้คำสั่งนี้แบบธรรมดา เราก็จะได้ string ของ ข้อความที่เป็นแบบที่เรียกว่า human readable date string ออกมาตามตัวอย่าง  เราสามารถระบุให้คำสั่ง date แสดงข้อความออกมาเป็นรูปแบบอื่นๆได้อีกด้วย เช่น แสดงเวลานับเป็นวินาทีเริ่มจาก เวลาอ้างอิงของระบบปฏิบัติการแบบ unix ที่เรียกกันโดยทั่วไปว่า epoch ซึ่งเวลาที่ว่านั่นก็คือ 1970-01-01 00:00:00 … ถ้าจะถาม ว่าทำไมเวลาอ้างอิง หรือ epoch ถึงต้องเป็นวัน/เวลานี้ด้วย  คำตอบคงจะต้องเท้าความถึงประวัติศาสตร์ของ Unix ค่อนข้างไกล … ซึ่งคงจะออกนอกเรื่องไปอีกพอสมควร เพราะฉะนั้นขอละไว้เพียงแค่นี้ก่อนก็แล้วกันครับ ผู้ที่สนใจลองหาอ่านเองโดยใช้ google ช่วยได้ครับ

    คำสั่ง date สามารถแสดงผลออกมาได้หลากหลายรูปแบบมาก รายละเอียดสามารถอ่านได้จาก man page ของคำสั่ง date นะครับ แต่สำหรับกรณีของการแสดงเวลาเป็นวินาทีนับจากเวลาอ้างอิง ก็สามารถใช้คำสั่ง

    date +%s

    รูปแบบ +%… จะเป็นการกำหนดรูปแบบการแสดงผล นะครับ ตัวอย่างเช่น บนเครื่องของผมขณะที่เขียนบันทึกอยู่ขณะนี้

     $ date;date +%s
     Tue Mar 26 02:50:49 ICT 2013
     1364241049

    เวลา 1364241049 วินาทีนับจาก epoch ก็เป็นเวลาเดียวกันกับ Tue Mar 26 02:50:49 ICT 2013 เพียงแต่แสดงในรูปแบบที่ต่างกัน

    เอาล่ะเราได้เวลาที่เป็น เวลาปัจจุบัน แสดงออกมาเป็นวินาทีแล้ว … แล้วทีนี้ ค่าเวลา uptime ล่ะ เราจะแปลง “6 days, 8:18” หรือ 6 วัน 8 ชม. 18 นาที มาเป็นตัวเลขวินาทียังไง … ดูแล้วก็น่าจะไม่ยากมากเท่าไหร่ … แต่ก็ไม่ง่าย …(ฮา)

    จริงๆแล้ว ก็มีวิธีง่ายๆอยู่ครับ ค่าของ uptime เราสามารถอ่านมาจากไฟล์ /proc/uptime ได้ โดยไม่ต้องไปแปลงจาก string ของ “N days, HH:MM” โดยตรง

     $ cat /proc/uptime
     549627.40 1106209.61

    ตัวเลขตัวแรกเป็น uptime ส่วนตัวที่สองเป็น cpu idle time

    เอ๊ะ.. ทำไม idle time ถึงมากกว่า uptime ล่ะ? … คือว่าเครื่องที่ผมใช้งานอยู่มันมี core ของ processor อยู่จำนวน 4 core ใน 1 cpu น่ะครับ ค่า idle time นับของ cpu ทุกตัวรวมกัน ตัวเลขก็เลยออกมาสูงกว่า cpu uptime

    เอาล่ะ เราสามารถ หาวัน/เวลา ที่เครื่องเริ่มต้นทำงานได้ โดยการใช้คำสั่งของ shell ได้คร่าวๆประมาณนี้ครับ

     NOW=`date +%s`
     UPTIME=`cat /proc/uptime | cut -f1 -d.`
     STARTTIME=`expr $NOW - $UPTIME`
     echo $STARTTIME

    เวลาของ UPTIME เราใช้เฉพาะหน่วยวินาทีโดยการใช้ จุดเป็นตัวคั่น แล้วตัดส่วนที่เหลือทิ้งไปทั้งหมด (รวมทั้ง idle time ด้วย) ส่วนการคำนวณ ก็ใช้คำสั่ง expr เป็นตัวช่วย

    แต่เวลาที่ได้เป็นตัวเลข จำนวนเป็นวินาทีนี่!! ดูไปก็ยังไม่รู้ว่ามันเป็นวัน/เวลาใหนกันแน่ … จะต้องแปลงตัวเลขกลับยังไงนี่

    ตรงนี้ไม่ยากครับ ใช้คำสั่ง date อีกนั่นแหละ เราสามารถใช้ option -d หรือ –date= สำหรับการระบุเวลาที่ต้องการเป็นเวลาอื่น แทนที่จะเป็นเวลา “ปัจจุบัน” ได้ โดยที่ ถ้าเราจะระบุเวลาเป็น จำนวนวินาทีนับจากเวลาอ้างอิง — epoch ก็ให้ใส่เครื่องหมาย @ หน้าตัวเลขนั้นครับ ซึ่ง สามารถลองได้ประมาณนี้

     $ date -d @0
     Thu Jan  1 07:00:00 ICT 1970
     $ date -d @1363691682
     Tue Mar 19 18:14:42 ICT 2013

    ถ้าใช้ @0 นั่นก็คือระบุเวลาอ้างอิงเลย ก็จะได้เป็นวันที่ 1 มค. 1970 เวลา 00:00:00 UTC — เวลาที่แสดงเป็น 07:00:00 ICT ซึ่งเป็นเวลาของประเทศไทย (=GMT+7)  และ เวลาที่เครื่องคอมพิวเตอร์ที่ผมใช้ reboot ครั้งสุดท้ายเมื่อ วันที่ 19 มีค. เวลา 18:14 น.

    ส่วนที่เหลือก็คือ รับข้อมูลแต่ละบรรทัดจาก คำสั่ง dmesg เข้ามาแล้วก็แปลงข้อมูลของ time stamp ที่อยู่ในรูปแบบ uptime ซึ่งอยู่ระหว่างเครื่องหมาย [] ข้างหน้าออกมา ส่วนนี้ สามารถใช้คำสั่ง read ช่วย เช่น dmesg บรรทัดสุดท้ายได้ข้อมูลออกมาเป็น

     $ dmesg | tail -1
     [288163.997319] br0: port 1(eth0) entering forwarding state

    คำสั่ง read จะอ่านค่ามาเก็บไว้ในตัวแปร โดยจะแบ่งโดยใช้ space

     $ dmesg | tail -1 | (read STAMP REST; echo "STAMP=$STAMP REST=$REST")
     STAMP=[288163.997319] REST=br0: port 1(eth0) entering forwarding state

    ในกรณีนี้ เราจะได้ค่าเวลา ซึ่งมี เครื่องหมาย [] คร่อม อยู่ในตัวแปร shell ชื่อ STAMP และ ส่วนที่เหลือทั้งหมด จะอยู่ในตัวแปร shell ที่ชื่อว่า REST

    ในส่วนของ เวลา อาจจะอยู่ในรูปแบบ  [ 0.00000] ก็ได้ ซึ่งจะมีช่องว่างอยู่หลัง ‘[‘ และก่อนตัวเลข ซึ่งสามารถกำจัดทิ้งได้ ด้วยคำสั่ง sed และ เครื่องหมาย ‘]’ ด้านหลังตัวเลขก็จะสามารถตัดทิ้งได้ด้วย sed เช่นเดียวกัน หรือ อาจจะใช้คำสั่ง tr  ด้วย option -d ก็ได้

    ตัวเลขที่ได้ ตัดเอามาเฉพาะส่วนที่เป็นวินาที แล้วเอามาบวกกับค่า STARTTIME โดยใช้คำสั่ง expr เป็นตัวช่วยเหมือนเดิม

    เอาล่ะ ใช้สิ่งที่ได้เรียนรู้จากข้างต้น เราก็จะสามารถเขียนเป็น shell script ออกมาได้ประมาณนี้

    #!/bin/sh
    DSTR1="+%b %d %H:%M:%S"                    # Syslog format Month Date Hour:Min:Sec
    DSTR2="+%Y-%m-%d %H:%M:%S"                 # ISO format
    NOW=`date +%s`                             # current timestamp in second
    UPTIME=`cat /proc/uptime | cut -f1 -d.`    # current uptime
    START=`expr $NOW - $UPTIME                 # start time of this machine
    cat - | sed 's/^\[ *//' |\
    while read STAMP REST; do
        SEC=`echo $STAMP | tr -d '[]' | cut -f1 -d.`
        TIME=`expr $START + $SEC`
        DATE=`date -d @$TIME "$DSTR1"`
        echo "$DATE $REST"
    done

    ส่วนที่เพิ่มขึ้นมาก็คือ การเลือกแสดงผล ออกมาเป้นรูปแบบเดียวกับ date/time stamp ที่ใช้ใน log file (DSTR1) หรือ จะใช้แบบ ISO format (DSTR2)

    วิธีการใช้งาน script ตัวนี้ ซึ่งสมมติให้มีชื่อว่า dmesg-datestamp ก็ใช้แบบนี้ครับ

    $ dmesg | ./dmesg-datestamp

    ซึ่งในกรณีที่มี ข้อความใน dmesg จำนวนมาก ก็อาจจะใช้เวลาค่อนข้างนานเพราะในแต่ละบรรทัด ของ dmesg จะต้องมีการคำนวณ เวลาและแปลงเป็นวันที่ใหม่ โดยการใช้คำสั่ง expr และ date ตามลำดับ ซึ่งสามารถลดเวลาที่ใช้ในการแสดงผล โดยเลือกเอาเฉพาะ dmesg ในบรรทัดที่เราสนใจได้ เช่น

    $ dmesg | grep eth0 | ./dmesg-datestamp

    หรืออาจจะ redirection ลงไปเก็บไว้ในไฟล์ แล้วอ่านจากไฟล์ แทนที่จะอ่านจากคำสั่ง dmesg ทุกครั้ง

    สำหรับคนที่ ทนอ่านมาจนถึงบรรทัดนี้ แล้วกำลังจะบอกผมว่า … จริงๆแล้ว ไม่ต้องทำถึงขนาดนั้นก็ได้ครับ คำสั่ง /bin/dmesg มี option -T ซึ่ง สามารถแปลง time stamp ให้อยู่ในรูปแบบ human readable ให้อยู่แล้วโดยไม่จำเป็นจะต้องพึ่งพา shell script ใดๆเลย

    แหะๆ ครับ Hit with Brick

    ผมก็ทราบแล้วครับ … แต่ว่านั่นมันหลังจากผมเขียน script เสร็จแล้ว (ฮา)

    บน Debian Linux ถ้าใช้ wheezy ก็จะสามารถใช้

    $ dmesg -T

    ได้เลย แต่บน Debian squeeze ซึ่งเป็น stable release ของ Debian ในตอนนี้ คำสั่ง dmesg ยังไม่สามารถใช้งาน option -T ได้ แต่ปัญหานี้ก็คงมีอีกไม่นานเท่าไหร่ เพราะ wheezy กำลังจะกลายเป็น stable release ในของ Debian ภายในไม่ช้า …

    แต่ … จะทำยังไงได้ล่ะ ก็เขียนไปแล้วนี่ …

    ส่วนใครที่อ่านมาจนถึงบรรทัดนี้แล้ว … ก็อ่านไปแล้วนี่ ช่วยอะไรไม่ได้เนอะ …

    ขอจบเอาดื้อๆแค่นี้แหละคร้าบ …

  • shell script for download virtualbox packages Part 2

    หลังจาก เกริ่นนำ เสียยืดยาว แต่ ยังไม่เข้าสู่เนื้อหาใน ภาคแรก
    ก็ขอเอามาต่อภาคสองที่นี่ …
    ความเดิมตอนที่แล้ว โจทย์มีอยู่ว่า

    เราต้องการ download package ของ virtualbox ตัวล่าสุด และต้องการไฟล์ package สำหรับ
    1. Windows
    2. Ubuntu โดยแยกเป็น
    2.1 Ubuntu 12.04 — precise pangolin
    2.2 Ubuntu 12.10 — quantal quetzal
    ทั้งสอง revision ต้องการ package สำหรับทั้ง i386 และ amd64 architecture
    3. Extension pack
    4. User manual
    5. ไฟล์ MD5SUMS สำหรับตรวจสอบไฟล์ ที่ download มา

    โดยไฟล์ทั้งหมดจะเก็บอยู่ใน http://download.virtualbox.org/virtualbox/$version
    ส่วนของตัวเลข $version นั้นเรามีวิธีการที่จะใช้ script ในการอ่านค่านั้นมาโดยอัตโนมัติเอาไว้แล้ว ใน ตอนแรกของบทความ ชุดนี้ (เรียกซะเป็นชุด แต่จริงๆแล้วก็มีแค่ 2 เท่านั้นแหละครับ)

    ไฟล์ที่ต้องการจริงๆนั้นมีอะไรบ้าง?
    1. Windows
    http://download.virtualbox.org/virtualbox/4.2.8/VirtualBox-4.2.8-83876-Win.exe
    2. Ubuntu
    2.1 Ubuntu 12.04 มี 2 file สำหรับ 2 architecture
    http://download.virtualbox.org/virtualbox/4.2.8/virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    http://download.virtualbox.org/virtualbox/4.2.8/virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb

    เอาเท่านี้ก่อน … จะเห็นว่า URL ที่จะใช้ในการเข้าถึงไฟล์นั้น จะซับซ้อนพอดู
    ส่วนแรกที่เหมือนกัน ก็คือ URL ที่ระบุไปถึง directory และ version คือ

    http://download.virtualbox.org/virtualbox/4.2.8/

    จะเหมือนกัน แต่หลังจากนั้น ส่วนของไฟล์จะได้เป็น

    VirtualBox-4.2.8-83876-Win.exe
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb

    และไฟล์ของ Ubuntu 12.10

    virtualbox-4.2_4.2.8-83876~Ubuntu~quantal_amd64.deb
    virtualbox-4.2_4.2.8-83877~Ubuntu~quantal_i386.deb

    นั่นแน่ะ ตัวเลข release เฉพาะของ Ubuntu 12.10 เองก็ยังต่างกัน ระหว่าง amd64 (83876) กับ i386 (83877) อีกต่างหาก ถ้าจะระบุชื่อเองโดยหาข้อมูล version/sub version/release บวกกับ architecture บวกกับการตั้งชื่อที่แตกต่างกัน (ตัวเล็ก/ใหญ่ ของชื่อ VirtualBox ระหว่าง Windows กับ Ubuntu) ก็คงจะได้ script ที่ซับซ้อนน่าดู

    Crying

    แต่จริงๆแล้วมีวิธีการที่ง่ายกว่านั้น

    ก่อนอื่นเรามา list ไฟล์ที่มีอยู่ออกมาดูกันก่อน เราสามารถใช้คำสั่ง

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/

    ก็จะเห็น ข้อมูลของหน้า download อยู่ใน format ของ html มีขยะอะไรที่เราไม่ต้องการเยอะแยะไปหมด เราต้องการเฉพาะบรรทัดที่อ้างถึงไฟล์ที่เราสามารถเอามาใช้ download ได้ ลองสังเกตดู เราก็จะเห็นว่า บรรทัดเหล่านั้นจะเริ่มบรรทัดด้วย ‘<A HREF=’ … งั้นเอาละ เราก็กรองมาเฉพาะบรรทัดเหล่านั้น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF'

    สวยงามขึ้น … แต่เราต้องการเฉพาะชื่อไฟล์ ไม่ได้ต้องการ html format ทั้งบรรทัด ในกรณีนี้ ส่วนที่เราต้องการอยู่ระหว่างเครื่องหมาย double qoute (“) คู่แรก ซึ่งเราใช้คำสั่ง cut ช่วยแยกสิ่งที่เราต้องการออกมาได้ โดยเพิ่มคำสั่งเป็น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF' | cut -f2 -d\"

    ผลลัพธ์คือ

    /virtualbox/
    MD5SUMS
    Oracle_VM_VirtualBox_Extension_Pack-4.2.8-83876.vbox-extpack
    Oracle_VM_VirtualBox_Extension_Pack-4.2.8.vbox-extpack
    SDKRef.pdf
    SHA256SUMS
    UserManual.pdf
    VBoxGuestAdditions_4.2.8.iso
    VirtualBox-4.2-4.2.8_83876_el4-1.i386.rpm
    VirtualBox-4.2-4.2.8_83876_el5-1.i386.rpm
    VirtualBox-4.2-4.2.8_83876_el5-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_el6-1.i686.rpm
    VirtualBox-4.2-4.2.8_83876_el6-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_fedora16-1.i686.rpm
    VirtualBox-4.2-4.2.8_83876_fedora16-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_fedora17-1.i686.rpm
    VirtualBox-4.2-4.2.8_83876_fedora17-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_fedora18-1.i686.rpm
    VirtualBox-4.2-4.2.8_83876_fedora18-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_mdv2010.0-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_mdv2010.0-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_mdv2011.0-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_mdv2011.0-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_openSUSE114-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_openSUSE114-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_sles10.1-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_sles10.1-1.x86_64.rpm
    VirtualBox-4.2-4.2.8_83876_sles11.0-1.i586.rpm
    VirtualBox-4.2-4.2.8_83876_sles11.0-1.x86_64.rpm
    VirtualBox-4.2.8-83876-Linux_amd64.run
    VirtualBox-4.2.8-83876-Linux_x86.run
    VirtualBox-4.2.8-83876-OSX.dmg
    VirtualBox-4.2.8-83876-SunOS.tar.gz
    VirtualBox-4.2.8-83876-Win.exe
    VirtualBox-4.2.8.tar.bz2
    VirtualBoxSDK-4.2.8-83876.zip
    virtualbox-4.2_4.2.8-83876~Debian~squeeze_amd64.deb
    virtualbox-4.2_4.2.8-83876~Debian~squeeze_i386.deb
    virtualbox-4.2_4.2.8-83876~Debian~wheezy_amd64.deb
    virtualbox-4.2_4.2.8-83876~Debian~wheezy_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~hardy_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~hardy_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~lucid_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~lucid_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~natty_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~natty_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~oneiric_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~oneiric_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~quantal_amd64.deb
    virtualbox-4.2_4.2.8-83877~Ubuntu~quantal_i386.deb

    สวยงามยิ่งกว่าเดิม … มี directory ที่เราไม่ต้องการโผล่มา 1 คือ “/virtualbox/” ซึ่งสามารถใช้ grep -v เพื่อทิ้งไปได้ แต่สำหรับกรณี ไม่จำเป็น

    จากไฟล์ที่ list ออกมา ถ้าพิจารณาดูหน่อยนึง เราก็จะเห็นว่า งานที่เราจำเป็นจะต้องทำ ไม่ต้องไปสร้าง ชื่อของไฟล์ให้ยุ่งยากซับซ้อนเกินไปก็ได้ เพราะใน list ที่เรามีอยู่ เราสามารถเลือก “grep” ส่วนที่เราต้องการได้เลย เช่น ไฟล์ package สำหรับ ubuntu 12.04 ก็สามารถเลือก grep โดยใช้ string “precise” ได้เลยเช่น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF' | cut -f2 -d\" | grep "precise"

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

    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb

    ถ้าต้องการ ubuntu 12.10 ด้วยล่ะ ก็เพิ่ม option -E สำหรับคำสั่ง grep และเปลี่ยน string ที่จะใช้ grep นิดหน่อยเป็น “precise|quantal” ก็จะได้คำสั่งเป็น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF' | cut -f2 -d\" | grep -E "precise|quantal"

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

    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb
    virtualbox-4.2_4.2.8-83876~Ubuntu~quantal_amd64.deb
    virtualbox-4.2_4.2.8-83877~Ubuntu~quantal_i386.deb

    อะฮ่า … งดงามเป็นอย่างยิ่ง โดยใช้ trick เดียวกันเราก็จะได้คำสั่ง สำหรับ “เลือก”ไฟล์ที่เราต้องการทั้งหมด ได้เป็น

    wget -q -O- http://download.virtualbox.org/virtualbox/4.2.8/ | grep '^<A HREF=' | cut -f2 -d\" | grep -E "Win|precise|quantal|MD5SUM|UserManual|Extension_Pack"

    โอเค คราวนี้เราก็พร้อมที่จะดัดแปลงมันเป็น shell script สำหรับการ download VirtualBox package ที่เราต้องการแล้วละครับ โดยส่ง list รายการของไฟล์ทั้งหมดไปเก็บไว้ในตัวแปร แล้วก็ใช้การวน loop download มาทีละไฟล์ประมาณนี้ครับ

    #!/bin/sh
    
    VERS="4.2.8"
    VBOXDL="http://download.virtualbox.org/virtualbox/$VERS"
    LIST=`    wget -q -O- $VBOXDL                        |\
        grep '^<A HREF='                        |\
        cut -f2 -d\"                            |\
        grep -E "Win|precise|quantal|MD5SUM|UserManual|Extension_Pack"    |\
        grep -v "Extension_Pack-${VERS}-"                `
    
    for file in $LIST; do
        wget ${VBOXDL}/${file}
    done

    บรรทัด grep -v “Extension_Pack-${VERS}-” ที่เพิ่มขึ้นมาก็เพื่อตัดให้เหลือไฟล์ extension pack ที่ต้อง download เพียงไฟล์เดียว เพราะทั้งสองไฟล์ถึงจะมีชื่อไฟล์ต่างกัน แต่จริงๆแล้วก็เป็นไฟล์เดียวกันครับ

    งานส่วนที่เหลือก็คือ รวมเอา script ในตอนแรก ที่ใช้ในการหา $VERS แบบอัตโนมัติ มารวมอยู่ใน script เดียวกันนี้ เพื่อว่าจะได้ไม่จำเป็นจะต้องมาเปลี่ยนหมายเลข version ใหม่ทุกครั้ง ก่อนที่จะ download
    หรือถ้าจะให้ดียิ่งไปกว่านั้น ก็คือ ให้ script download ไฟล์ทั้งหมดมาโดยอัตโนมัติ และ ส่ง email แจ้งว่า ได้ download ไฟล์ทั้งหมดมาเรียบร้อยแล้ว

    อันนี้ ทิ้งไว้เป็นโจทย์ ให้ลองหัดทำกันดูเก็แล้วกันนะครับ

    ถ้ามีปัญหาแก้ไม่ได้ ก็ขอให้ comment มาครับ เผื่อว่า ผมจะได้ idea สำหรับเขียน blog ใหม่เพิ่ม เพราะว่า ถ้าปล่อยให้คิดเอง ก็คิดไม่ออกเหมือนกัน (ฮา)

    สุดท้ายนี้ ขอขอบคุณเจ้าของ email ต้นเรื่องนะครับ ที่ทำให้ผมเขียน blog เพิ่มขึ้นได้อีก 2 บทความ ถ้าไม่ได้คำถามดีๆ ผมก็ไม่มีอะไรให้เขียนจริงๆครับ ขอบคุณอีกครั้งครับ

  • shell script for download virtualbox packages

    หลังจากบรรยายเรื่อง Shell script ไปในสัปดาห์ที่แล้ว ด้วยหัวข้อเรื่องที่มีอยู่ค่อนข้างเยอะกว่าที่คิดไว้ และใช้เวลาบรรยายไปอย่างไม่ค่อยมีประสิทธิภาพสักเท่าไหร่ ทำให้เนื้อเรื่องบางส่วน ต้องทิ้งเอาไว้ ไม่ได้พูดต่อให้จบ ปล่อยคนที่เอา slide มาอ่านงงไปก็แล้วกันว่า มันคือเรื่องอะไรกัน

    เรื่อง slide ก็ … เหอะ … เดี๋ยวมีโอกาส ค่อยกลับไปปรับปรุงมันอีกที ผมยังพอมี idea อยู่บ้างว่าจะแก้ไขมันยังไง แต่เรื่องหนึ่งซึ่งจริงๆแล้ว มีปัญหาตั้งแต่ก่อนเริ่มบรรยายแล้วว่า จะยกตัวอย่างโจทย์ปัญหาอย่างไรดี ที่จะทำให้เห็น วิธีการที่ผมใช้ในการ “เขียน” shell script ตัวนึงออกมาได้ ตัวอย่างที่ยกให้ดูในตอนแรกของ การบรรยาย เป็นแค่ตัวอย่างหลอกๆ ที่เขียนไปเพื่อให้เห็น รูปแบบ ของการใช้ shell script แต่จะว่าไป ที่ผมใช้งานอยู่ปัจจุบัน ก็ไม่ได้ตัวอย่างที่ว่านั่น จะใช้ตัวอย่างอื่นที่มี ตัวอย่างเช่น psuautosigned ก็คงต้องอธิบายในเรื่องอื่นกันยาว ก่อนที่จะวกกลับมาที่เรื่องของ shell script ได้

    จนกระทั่งวันนี้ ได้รับ email ฉบับนี้มา

    > อาจารย์ครับ
    >    มีเรืองปรึกษครับ
    >
    > สิ่งที่ต้องการคือ wget file หลายๆ  file ตามข้างล่างนี้
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/VirtualBox-${version1}-83876-Win.exe
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/virtualbox-4.2_${version1}-83877~Ubuntu~quantal_i386.deb
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/virtualbox-4.2_${version1}-83876~Ubuntu~quantal_amd64.deb
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/virtualbox-4.2_${version1}-83876~Ubuntu~precise_i386.deb
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/virtualbox-4.2_${version1}-83876~Ubuntu~precise_amd64.deb
    > wget http://download.virtualbox.org/virtualbox/${version1}/MD5SUMS
    > wget
    > http://download.virtualbox.org/virtualbox/${version1}/Oracle_VM_VirtualBox_Extension_Pack-${version1}-83876.vbox-extpack
    > wget http://download.virtualbox.org/virtualbox/${version1}/UserManual.pdf
    >
    > ผมเขียน  script แบบนี้
    > version1=4.2.8
    > url1=http://download.virtualbox.org/virtualbox/${version1}
    > for i in Win  quantal  precise Oracle MD5 User
    >     do
    >        wget  ${url1}/*{i}*
    >    done
    > ไม่ work ครับ
    > ขอคำแนะนำด้วยครับ

    ผมตอบกลับไปว่า

    |        ใช้ *{i}* หรือ *${i}* ไม่ได้ครับ เพราะ * ที่ใช้บน command line
    |        ปกติ จะเป็นตัวอักษรที่ตีความโดย shell ที่เราใช้งานอยู่ ซึ่งโดยทั่วไป
    |        มันก็จะพยายาม match กับ ชื่อไฟล์ ใน directory บนเครื่องของเราครับ
    |
    |        ทีนี้ ตอนท่ีส่งให้กับ wget ถ้า shell ไม่สามารถ expand ตัว * ออกมาเป็น
    |        ไฟล์ได้ มันก็จะส่ง * ไปยังเครื่องปลายทาง ซึ่งก็จะเปรียบเทียบแบบ literal
    |        คือเป็นตัวอักษร * โดยตรงโดยไม่ได้พยายาม match กับไฟล์ที่มีอยู่ใน
    |        directory นั้นๆ
    |
    |        วิธีการหนึ่งที่อาจจะใช้สำหรับการ download ทั้งหมดมาได้ ก็คือใช้ option -r
    |        แต่ว่านั่นก็จะได้ไฟล์ ที่เราไม่ต้องการมาอีกหลายๆไฟล์
    |
    |       สำหรับกรณีนี้ วิธีการที่ สามารถใช้ในการแก้ปัญหาได้ก็คือ อ่าน list ของไฟล์
    |       ที่มีอยู๋ทั้งหมดมาก่อน แล้วค่อยมาเลือกชื่อไฟล์ที่เราต้องการ เอามาเก็บไว้ใน
    |       list ที่เราต้องการ download อีกรอบนึง ครับ
    |
    |       อันที่จริงแล้ว นี่เป็นคำถามที่ดีมาที่จะใช้ยกตัวอย่างในเรื่องของการใช้งาน
    |       shell script เลยทีเดียวครับ
    |
    |       ผมกำลังหา หัวข้อเรื่องที่เหมาะสมสำหรับเขียนเป้น blog ลงใน psu sysadmin
    |       พอดีเลยครับ ผมขอเอาคำตอบไปตอบในโน้นนะครับ

    จริงๆแล้วแอบร้องยูเรก้าอยู่ในใจ เพราะนี่แหละสิ่งที่ผมต้องการ ไม่ต้องรอคำอนุญาตจากเจ้าของ email ผมก็รีบเอามา post ที่นี่ทันทีถ้าไม่มีคำถามดีๆ ผมก็ไม่มีไอเดีย อะไรที่จะเอามาเขียนเป็น blog เหมือนกันครับ ของคุณเจ้าของ email ที่ส่งมาถามเป็นอย่างมากเลยครับขอเริ่มจากอธิบายโจทย์ใหม่อีกสักรอบก่อนนะครับโจทย์ คือต้องการที่จะ download package ของ virtualbox ใหม่ล่าสุดที่มีอยู่บน site ของ virtualbox มาเก็บไว้ที่ local เพื่อให้การติดตั้ง package บนเครื่องคอมพิวเตอร์ในเครือข่าย ซึ่งมีอยู่หลายเครื่อง จะได้ไม่ต้องไป download มาสำหรับแต่ละเครื่องให้เปลือง bandwidthถ้าหากว่า เครื่องคอมพิวเตอร์ที่ใช้ เป็น Linux และติดตั้ง Ubuntu Linux หรือ Debian Linux และต้องการ
    VirtualBox ตัวที่ทันสมัย แต่ไม่ถึงกับต้องการตัว “ล่าสุด” จริงๆ ปัญหานี้ก็สามารถแก้ได้ง่ายๆ โดยติดตั้ง
    Virtualbox บนเครื่อง โดยการใช้คำสั่ง

    sudo apt-get install virtualbox

    ซึ่งบนเครื่อง Debian Wheezy ที่ผมใช้งานอยู่ปัจจุบัน ก็จะมี Virtualbox 4.1.8 ให้ใช้ติดตั้งได้เลย โดยไม่ต้อง download มาจาก website ของ virtualbox

    แต่ถ้าต้องการ virtualbox version ล่าสุด ซึ่ง version stable ล่าสุด ซึ่งประกาศ release อยู่บน website ของ virtualbox (ณ วันนี้ 6 มีค. 2556) คือ 4.2.8 ซึ่งก็ … อะ แฮ่ม … ต่างกันไม่มากเท่าไหร่

    แต่ถ้าต้องการ version ล่าสุด หรือ package สำหรับ Windows หรือ Mac OSX ด้ว ก็คงต้องใช้วิธีการ download มาจาก website ของ virtualbox โดยตรงจาก https://www.virtualbox.org/wiki/Downloads ครับ

    กลับมาที่ โจทย์ ต่อ

    เราต้องการ download package ของ virtualbox ตัวล่าสุด และต้องการไฟล์ package สำหรับ
    1. Windows
    2. Ubuntu โดยแยกเป็น
    2.1 Ubuntu 12.04 — precise pangolin
    2.2 Ubuntu 12.10 — quantal quetzal
    ทั้งสอง revision ต้องการ package สำหรับทั้ง i386 และ amd64 architecture
    3. Extension pack
    4. User manual
    5. ไฟล์ MD5SUMS สำหรับตรวจสอบไฟล์ ที่ download มา

    ไฟล์ของ virtualbox ซึ่งสร้างเป็น link ไว้ ในหน้า download จะเก็บอยู่บน http://download.virtualbox.org/virtualbox/4.2.8/ สำหรับ version 4.2.8

    ซึ่งถ้าเรา list มาดูจะได้ ประมาณนี้

    [DIR]  Parent Directory
    [   ]  MD5SUMS                                                          01-Mar-2013 02:11     3K    
    [   ]  Oracle_VM_VirtualBox_Extension_Pack-4.2.8-83876.vbox-extpack     27-Feb-2013 12:56    10M    
    [   ]  Oracle_VM_VirtualBox_Extension_Pack-4.2.8.vbox-extpack           27-Feb-2013 12:56    10M    
    [   ]  SDKRef.pdf                                                       27-Feb-2013 12:55     2M    
    [   ]  SHA256SUMS                                                       01-Mar-2013 02:12     5K    
    [   ]  UserManual.pdf                                                   27-Feb-2013 12:55     5M    
    [BIN]  VBoxGuestAdditions_4.2.8.iso                                     27-Feb-2013 13:00    54M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el4-1.i386.rpm                        27-Feb-2013 12:45    82M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el5-1.i386.rpm                        27-Feb-2013 12:45    81M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el5-1.x86_64.rpm                      27-Feb-2013 13:01    81M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el6-1.i686.rpm                        27-Feb-2013 12:45    67M    
    [   ]  VirtualBox-4.2-4.2.8_83876_el6-1.x86_64.rpm                      27-Feb-2013 13:01    67M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora16-1.i686.rpm                   27-Feb-2013 12:45    66M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora16-1.x86_64.rpm                 27-Feb-2013 12:45    66M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora17-1.i686.rpm                   27-Feb-2013 12:45    66M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora17-1.x86_64.rpm                 27-Feb-2013 12:45    66M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora18-1.i686.rpm                   27-Feb-2013 12:46    68M    
    [   ]  VirtualBox-4.2-4.2.8_83876_fedora18-1.x86_64.rpm                 27-Feb-2013 12:46    68M    
    [   ]  VirtualBox-4.2-4.2.8_83876_mdv2010.0-1.i586.rpm                  27-Feb-2013 12:46    69M    
    [   ]  VirtualBox-4.2-4.2.8_83876_mdv2010.0-1.x86_64.rpm                27-Feb-2013 12:46    69M    
    [   ]  VirtualBox-4.2-4.2.8_83876_mdv2011.0-1.i586.rpm                  27-Feb-2013 12:46    61M    
    [   ]  VirtualBox-4.2-4.2.8_83876_mdv2011.0-1.x86_64.rpm                27-Feb-2013 12:46    61M    
    [   ]  VirtualBox-4.2-4.2.8_83876_openSUSE114-1.i586.rpm                27-Feb-2013 12:46    61M    
    [   ]  VirtualBox-4.2-4.2.8_83876_openSUSE114-1.x86_64.rpm              27-Feb-2013 12:46    61M    
    [   ]  VirtualBox-4.2-4.2.8_83876_sles10.1-1.i586.rpm                   27-Feb-2013 12:46    80M    
    [   ]  VirtualBox-4.2-4.2.8_83876_sles10.1-1.x86_64.rpm                 27-Feb-2013 12:46    80M    
    [   ]  VirtualBox-4.2-4.2.8_83876_sles11.0-1.i586.rpm                   27-Feb-2013 12:46    73M    
    [   ]  VirtualBox-4.2-4.2.8_83876_sles11.0-1.x86_64.rpm                 27-Feb-2013 12:47    73M    
    [BIN]  VirtualBox-4.2.8-83876-Linux_amd64.run                           27-Feb-2013 12:55    79M    
    [BIN]  VirtualBox-4.2.8-83876-Linux_x86.run                             27-Feb-2013 12:55    79M    
    [   ]  VirtualBox-4.2.8-83876-OSX.dmg                                   27-Feb-2013 12:55   103M    
    [   ]  VirtualBox-4.2.8-83876-SunOS.tar.gz                              27-Feb-2013 12:55   113M    
    [BIN]  VirtualBox-4.2.8-83876-Win.exe                                   27-Feb-2013 12:55    93M    
    [   ]  VirtualBox-4.2.8.tar.bz2                                         28-Feb-2013 02:22    72M    
    [   ]  VirtualBoxSDK-4.2.8-83876.zip                                    27-Feb-2013 13:00     9M    
    [   ]  virtualbox-4.2_4.2.8-83876~Debian~squeeze_amd64.deb              27-Feb-2013 09:00    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Debian~squeeze_i386.deb               27-Feb-2013 08:49    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Debian~wheezy_amd64.deb               27-Feb-2013 10:20    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Debian~wheezy_i386.deb                27-Feb-2013 10:05    61M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~hardy_amd64.deb                27-Feb-2013 12:12    82M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~hardy_i386.deb                 27-Feb-2013 11:59    79M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~lucid_amd64.deb                27-Feb-2013 09:42    71M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~lucid_i386.deb                 27-Feb-2013 09:33    71M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~natty_amd64.deb                27-Feb-2013 08:38    59M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~natty_i386.deb                 27-Feb-2013 08:26    59M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~oneiric_amd64.deb              27-Feb-2013 11:46    59M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~oneiric_i386.deb               27-Feb-2013 11:30    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~precise_amd64.deb              27-Feb-2013 09:24    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~precise_i386.deb               27-Feb-2013 09:10    60M    
    [   ]  virtualbox-4.2_4.2.8-83876~Ubuntu~quantal_amd64.deb              27-Feb-2013 08:56    59M    
    [   ]  virtualbox-4.2_4.2.8-83877~Ubuntu~quantal_i386.deb               01-Mar-2013 01:45    60M

    ถ้าต้องการ download มาทั้งหมด ก็ทำได้โดยการใช้คำสั่ง

     wget -r -np -nH --cut-dirs=2 http://download.virtualbox.org/virtualbox/4.2.8/

    แต่ก็จะเห็นว่าเราจะได้ไฟล์จำนวนมากที่เราไม่ต้องการมาด้วย เราต้องการไฟล์จริงๆเพียงไม่กี่ไฟล์ เพราะฉะนั้น ก็ต้องทำงานเพ่ิมขึ้นอีกหน่อยนึง

    แต่ก่อนที่จะเริ่ม จากการ download ไฟล์จาก directory ที่เราต้องการ ซึ่งเรารู้ตำแหน่งที่แน่นอนอยู่แล้ว จากการสำรวจแบบ manual มาตั้งข้อสมมติฐานก่อนว่า ถ้าเมื่อไหร่ virtualbox มีการ release version ใหม่ออกมา เราก็ควรที่จะรู้ได้โดยอัตโนมัติ หรืออย่างน้อย เราก็ไม่น่าจะจำเป็นที่จะต้องมาเขียน script ใหม่ … คำถามคือ เราจะรู้ได้ใหม? และ ถ้าได้ เราจะรู้ได้อย่างไร

    คำตอบแรกคือ ได้ คำตอบถัดมามี อยู่หลายทางเลือกครับ แต่ถ้าเอาแบบไม่ซับซ้อนมากนัก วิธีการที่ผมใช้แบบ manual อยู่ก็คือ เข้าไปดูที่ https://www.virtualbox.org/wiki/Downloads ซึ่งในหน้าที่ก็จะ list ตัว package ของ virtualbox ที่จะให้ download สำหรับ OS ต่างๆ และจะมี ระบุ version เอาไว้ด้วย

    ในบรรทัดที่ใช้สำหรับระบุ URL ให้ download ของ Linux ก็จะเขียนไว้ว่า

    VirtualBox 4.2.8 for Linux hosts

    ตัวเลข 4.2.8 คือ version ซึ่งจะเปลี่ยนไปเรื่อยๆ เมื่อ Virtualbox มีการ update เป็น version ใหม่
    เราสามารถ เอาตัวเลขนี้ มาได้ โดยการใช้ความร่วมมือระหว่าง โปรแกรม 2 ตัว คือ wget กับ grep โดยที่คำสั่ง

    wget -q -O- http://www.virtualbox.org/wiki/Downloads

    เราจะได้ ข้อมูลของ page ดังกล่าวอยู่ในรูป html ปรากฏขึ้นบนหน้าจอ … เราต้องการเฉพาะบรรทัดที่ระบุ URL สำหรับ Linux ก็สามารถใช้ grep สำหรับ ตัดมา เฉพาะบรรทัดนั้นได้ โดยการใช้คำสั่ง

    wget -q -O- http://www.virtualbox.org/wiki/Downloads | grep 'for Linux hosts'

    ซึ่งจะได้ผลลัพธ์ออกมาเป็น

    </li><li><strong><a href="/wiki/Linux_Downloads">VirtualBox 4.2.8 for Linux hosts</a></strong>

    และ โดยการใช้คำสั่ง sed เพื่อที่จะตัดข้อความข้างหน้า ตั้งแต่ <li><s… ไปจนถึง VirtualBox ทิ้งไป ก็สามารถเพิ่มคำสั่งเข้าไปเป็น

    wget -q -O- http://www.virtualbox.org/wiki/Downloads  |\
    grep 'for Linux hosts' |\
    sed -e 's/^.*VirtualBox //'

    ก็จะได้ผลลัพธ์ออกมาเป็น

    4.2.8 for Linux hosts</a></strong>

    คราวนี้ เพิ่มส่วนของการกำจัดข้อความด้านหลังตัวเลข version โดยใช้ expression ที่สองของ sed ก็จะได้คำสั่งเป็น

    wget -q -O- http://www.virtualbox.org/wiki/Downloads  |\
     grep 'for Linux hosts' |\
     sed -e 's/^.*VirtualBox //' -e 's/ for Linux hosts.*$//'

    และได้ผลลัพธ์ออกมาเป็น

    4.2.8

    สวยงามไม่มีที่ติ Smile
    เอาน่า … เชื่อผมหน่อยน่า บอกว่าสวยก็สวยสิ!

    หลังจากพยายามมาสักพักเราก็ได้ตัวเลข version มา … ซึ่งจะเสียเวลากับมันมากไปหรือเปล่า? …
    เอาน่า โดยตัวเลขที่มีอยู่นี้ เราสามารถเขียน script สำหรับการตรวจสอบว่า Virtualbox มีการ update version ใหม่หรือเปล่า โดยการเอา code ข้างต้น มาปรับให้เป็น script ได้ประมาณนี้

    #!/bin/sh
    
    VBOXWIKIDL="http://www.virtualbox.org/wiki/Downloads"
    
    getVBoxVersion() {
        VERS=`wget -q -O- $VBOXWIKIDL          |\
              grep 'for Linux hosts'           |\
              sed  -e 's/^.*VirtualBox //'      \
                   -e 's/ for Linux hosts.*$//' `
    }
    
    getVBoxVersion
    echo "Last VirtaulBox Version = $VERS"

    เขียนส่วนของ code ให้ execute ใน backtick เพื่อส่งค่ามาให้กับตัวแปร shell VERS ซึ่งเราจะเอาไปใช้ต่อไป ใช้ตัวแปร VBOXWIKIDL สำหรับการระบุ URL ของ download page เพื่อให้ code อ่านง่ายขึ้น และเขียนในรูปแบบของ shell function เพื่อที่เราจะเอาฟังก์ชันนี้ไปใช้ในงานอื่นต่อไปในอนาคต

    กำหนด ชื่อให้ script เป็น vboxvers แล้วกำหนดให้มัน execute ได้โดยการใช้คำสั่ง

    chmod +x vboxvers

    และทดสอบ

    ./vboxvers

    เราก็จะได้ script สำหรับแสดง version ล่าสุดของ VirtualBox ที่มีให้ download จาก website ของ VirtualBox … แต่ถ้าให้ดีขึ้นไปกว่านี้ ตัว script นี้ควรจะทำงานโดยอัตโนมัติ โดยการตรวจสอบวันละครั้ง และถ้ามี version ที่ใหม่กว่า ก็ค่อยส่ง email มาแจ้งเรา

    ซึ่งเราสามารถทำได้โดย การเพิ่ม code สำหรับการเก็บข้อมูล version ล่าสุดที่เคยตรวจสอบเอาไว้ในไฟล์ และเปรียบเทียบ ตัวเลขนั้นกับ version ใหม่ที่มี สมมติให้ไฟล์นั้นชื่อ .vboxvers.txt เก็บไว้ใน HOME ของเราเอง

    FILE="$HOME/.vboxvers.txt"

    ถ้ามีไฟล์นั้นอยู่ เราก็อ่านค่า version ล่าสุดที่เคยตรวจสอบมาจากไฟล์นั้น แต่ถ้าไม่มีไฟล์ ก็กำหนดค่า version เริ่มต้นให้เป็น “0.0.0” ซะ

    if [ ! -f "$FILE" ]; then
            LAST="0.0.0"
    else
            LAST=`cat $FILE`
    fi

    หลังจากนั้น เราก็จะอ่านค่า version ล่าสุดจาก web ของ VirtualBox มาโดยใช้ ฟังก์ชัน getVBoxVersion ที่เขียนเอาไว้แล้ว

    เอาค่าที่ได้มาเปรียบเทียบกัน ถ้าไม่เท่ากัน ก็ส่ง email ให้กับ address ที่กำหนดเอาไว้

    if [ "$LAST" != "$VERS" ]; then
            echo "New VirtualBox $VERS available" |\
            mail -s "VirtualBox $VERS" $EMAIL
            # Save new version
            echo "$VERS" > $FILE
    fi

    ซึ่งก็จะได้ script สุดท้ายมีหน้าตาประมาณนี้ครับ

    #!/bin/sh
    
    VBOXWIKIDL="http://www.virtualbox.org/wiki/Downloads"
    FILE="$HOME/.vboxvers.txt"
    EMAIL="me@example.com" # Change to your email address!
    
    getVBoxVersion() {
        VERS=`wget -q -O- $VBOXWIKIDL          |\
              grep 'for Linux hosts'           |\
              sed  -e 's/^.*VirtualBox //'      \
                   -e 's/ for Linux hosts.*$//' `
    }
    
    # Check whether we have version file
    # if not use initial 0.0.0 version
    # if there is, then get the last version from that file
    if [ ! -f "$FILE" ]; then
            LAST="0.0.0"
    else
            LAST=`cat $FILE`
    fi
    
    # Then get lastest version from VirtualBox web
    getVBoxVersion
    
    # Then check and report
    if [ "$LAST" != "$VERS" ]; then
            echo "New VirtualBox $VERS available" |\
            mail -s "VirtualBox $VERS" $EMAIL
            # Save new version
            echo "$VERS" > $FILE
    fi

    ว้า … ยังไม่ไปถึงใหนเลย … แต่ชักจะยาวเกินไปแล้ว ขอตัดจบแค่นี้ก่อนครับ เดี๋ยวจะกลับมาต่อเรื่องจะ download package ไฟล์ที่เราต้องการจาก download.virtualbox.org ได้ยังไง ใน blog ถัดไปครับ Exciting (กรรมวิธีในการเพิ่มจำนวน blog ทั้งที่มีเนื้อหาที่จะเล่าเท่าเดิม … (ฮา) …)

  • การใช้ fail2ban สำหรับการตั้งรับ DNS Brute Force Query Attack

    ต่อเนื่องจาก บทความนี้

    หลังจากวิธีการใช้งาน blackhole เพื่อให้ bind9 ไม่ตอบกลับ query ที่ client ส่งมา แล้วพบว่าวิธีการนี้
    จะมีปัญหากับการโจมตีแบบ DDoS ดังนั้น เราก็ต้องรับมือกับการโจมตีแบบนี้ด้วยเครื่องมืออื่นๆแทน

    เครื่องมืที่ผมใช้งานแล้วพบว่ามีประโยชน์มากตัวนึง สำหรับการป้องกันการโจมตีแบบอัตโนมัติก็คือ fail2ban ซึ่งบน Debian/Ubuntu สามารถติดตั้งได้ด้วยคำสั่ง

    $ sudo apt-get install fail2ban

    ซึ่งจะสร้าง configuration file ของ fail2ban ขึ้นมาอยู่ใน directory /etc/fail2ban
    configuration ของ fail2ban จะแบ่งเป็นส่วนของการตรวจจับ (โดยการใช้ regular expression) จาก log file ที่กำหนดไว้ (ซึ่งจะอยู่ใน directory /etc/fail2ban/filter.d)  และสามารถ กำหนด action (ซึ่งจะอยู่ใน directory /etc/fail2ban/action.d) ซึ่งใช้ในการ ban  การเข้าใช้งาน service ของ server ของเรา หรือตอบสนองแบบอื่นๆ เช่นส่ง email ไปเตือน admin ได้ มี action ที่ได้กำหนดเอาไว้แล้วหลายรูปแบบ ในที่นี้ ผมจะเลือกใช้ iptables filter สำหรับการ filter query packet ที่จะเข้ามา ซึ่ง config นี้ จะอยู่ในไฟล์ /etc/fail2ban/action.d/iptables.conf

    ส่วนของการ ตรวจจับ จะมีตัวอย่างของการ เขียน expression เอาไว้แล้วในไฟล์ตัวอย่าง ที่มีอยู่ใน /etc/fail2ban/filter.d ซึ่งสำหรับกรณีนี้ ผมจะสร้างไฟล์ขึ้นมาใหม่ชื่อ named-query-dos.conf โดยจะมีข้อมูลในไฟล์ดังต่อไปนี้

    [Definition]
    failregex = client <HOST>#.+: query: isc.org IN ANY \+ED .+
    ignoreregex =

    โดยบรรทัด failregex จะเป็น regular expression ที่ได้มาจาก logfile /var/log/named/query.log ซึ่งจะมีรูปแบบของการ query ที่แน่นอน นั่นคือ query “isc.org” แบบ ANY และมี flag ของการ query คือ ‘ED’

    28-Nov-2012 11:23:54.063 client 46.160.80.232#25345: query: isc.org IN ANY +ED

    ส่วน filed ของ วันที่/เวลา จะเปลี่ยนไปเรื่อยๆตามเวลาของการ query ที่เกิดขึ้น หมายเลข ip ของ client จะถูก match กับ <HOST> และส่งกลับไปให้ action ซึ่งจะนำเอาไปใช้ต่อไป

    ส่วนของ action เราจะใช้ iptables สำหรับการ filter ซึ่งจะมี config กำหนดเอาไว้แล้ว ซึ่งไม่ต้องวเปลี่ยนแปลงอะไร ส่วนของการ config ที่จะต้องเพิ่มขึ้นมาก็คือ ระบุให้ตัว fail2ban รู้ว่าจะต้องเอา config ส่วนนี้ไปใช้ โดยการเพิ่มข้อมูลต่อไปนี้ เข้าไปในไฟล์ /etc/fail2ban/jail.conf

    [named-query-dos]
    enaled = true
    banaction = iptables
    port = domain,53
    protocol = udp
    filter = named-query-dos
    logpath  = /var/log/named/query.log
    bantime  = 86400
    maxretry = 5

    ซึ่งจาก config ที่เพิ่มเข้าไป (หรืออาจจะสร้างเป็นไฟล์ใหม่ ในชื่อว่า jail.local ก็ได้)
    [named-query-dos] จะเป็นการระบุว่า config ส่วนนี้ เป็การเริ่มต้น section ใหม่
    enabled = true จะระบุว่าให้เอา config นี้ไปใช้ ซึ่งเราสามารถ disable config ส่วนนี้ได้ โดยไม่ต้องลบ config ออก เพียงแต่เปลี่ยนค่า enable = false
    banaction = iptables เป็นการเลือก action ที่จะใช้ ban client ที่เข้ามาโจมตี
    port = domain,53
    protocol = udp
    ทั้ง port และ protocol จะเป็นการระบุข้อมูลการ ban ให้กับ iptables
    filter = named-query-dos เป็นการเลือก filter (match regular express ที่จะใช้ ซึ่งในที่นี้ก็เป็นการอ้างถึงไฟล์ /etc/fail2ban/filter.d/named-query-dos.conf)
    logpath  = /var/log/named/query.log เป็นการระบุชื่อไฟล์ ที่เป็น input ของการ match expression ซึ่งในที่นี้ก็คือไฟล์ ที่เรากำหนดให้ bind9 สร้างขึ้น
    bantime  = 86400 เป็นระยะเวลาที่ unban สำหรับ ip นั้นๆหน่วยเป็นวินาที
    maxretry = 5 จำนวนครั้งของการ query ที่เกิดขึ้น ก่อนที่จะเริ่ม ban

    ในที่นี้ ระยะเวลาของการ ban — bantime ผมกำหนดให้เท่ากับ 24 ชม. ซึ่งนานพอที่จะป้องกันการกลับเข้ามาโจมตีใหม่ในเวลาอันสั้น เราอาจจะไม่ยกเลิกการ ban เลยก็ได้ แต่สำหรับ iptables แล้วมันจะทำให้ tables โตขึ้นเรื่อยๆ ซึ่งจะทำให้การ process packet ช้าลง เพราะฉะนั้นเวลา ban 24 ชม. น่าจะเหมาะสม และถ้ามีการโจมตีเข้ามาอีก ก็จะถูก ban อีกในรอบ 24 ชม.ถัดไป

    maxretry กำหนดค่า default เป็น 3 ผมเพิ่มให้เป็น 5 เพื่อ ยืนยันว่าเป็นการโจมตีจริง ไม่ใช่ query ผิดพลาด ซึ่งอาจจะเกิดขึ้นได้

    หลังจาก config เสร็จแล้ว เราก็สั่งให้ fail2ban เริ่มต้นทำงานได้โดยใช้คำสั่ง

    $ sudo service fail2ban start

    เราสามารถตรวจสอบผลของการทำงานของ fail2ban จาก logfile ได้ โดยการใช้คำสั่ง

    $ sudo tail -f /var/log/fail2ban.log

    เท่าที่ผมใช้อยู่สามารถ จัดการกับการโจมตีที่เกิดขึ้นได้อย่างดีครับ

  • ตั้งรับและตอบโต้การโจมตี DNS Brute Force Query Attack

    ต่อเนื่องจาก บทความนี้

    หลังจากรู้แล้วว่า DNS Server ของเราถูกโจมตีล่ะนะ
    ทีนี้จะตอบโต้อย่างไรดี?

    ถ้าหากการโจมตีมันไม่ได้เป็น distribution คือตรวจสอบแล้วมาจาก host เพียงตัวเดียวหรือไม่กี่ตัว ก็สามารถตอบโต้แบบง่ายๆได้ โดยใช้ความสามารถของ bind9 เอง bind9 จะมี option ที่จะสามารถ block การ query จาก client ได้ โดยสามารถระบุเป็น ip เดี่ยวๆ หรือเป็น block ของ ip network โดยการเพิ่มเป็น blackhole ใน named.conf.options แบบนี้ครับ

    สมมติ options config เดิมของ bind9 คือ

    options {
        directory “/var/cache/bind”;
        forward only;
        forwarders {
             192.100.77.2;
             192.100.77.5;
        };
        auth-nxdomain no;    # conform to RFC1035
        listen-on-v6 { any; };
    };

    เราสามารถเพิ่ม

    include “/etc/bind/blackhole.list”;

    เข้าไปก่อน บรรทัด “};” ซึ่งเป็นบรรทัดล่างสุดของ options เป็น

    options {
        directory “/var/cache/bind”;
        forward only;
        forwarders {
             192.100.77.2;
             192.100.77.5;
        };
        auth-nxdomain no;    # conform to RFC1035
        listen-on-v6 { any; };
    include “/etc/bind/blackhole.list”;
    };

    โดยข้อมูลในไฟล์ /etc/bind/blackhole.list จะมีข้อมูลดังนี้

    blackhole {
        174.127.92.85;
        31.210.155.237;
        178.32.76.101;
       …
    };

    ซึ่งวิธีการนี้ ทำให้เราสามารถแก้ไขเฉพาะไฟล์ blackhole.list โดยไม่ต้องไปแก้ไข named.conf.options เมื่อมีการโจมตีโดยใช้ ip address ใหม่เกิดขึ้น

    อย่างไรก็ตาม ผมพบว่า วิธีการนี้ ไม่ค่อยได้ผลสักเท่าไหร่ ถ้าการโจมตีเป็นแบบ DoS หรือ DDoS เพราะ ผู้ที่โจมตี ไม่ได้สนใจข้อมูลที่ DNS Server ของเราจะส่งกลับไป เพียงแต่ต้องการทำให้ Server ทำงานหนักขึ้นเท่านั้น การระบุ blackhole list จะทำให้ DNS Server ส่งคำตอบกลับไปยัง client ที่ query มาว่า REFUSED แต่ก็จะยังมีการตอบกลับ และมีการบันทึกการ query ลงสู่ record อยู่
    นอกจากนี้ ถ้าเป็น DDoS ซึ่งจะมี client ที่มี ip address ใหม่ๆ โจมตีเข้ามาเรื่อยๆ การแก้ไขไฟล์ blackhole.list เพื่อให้ทันสมัยอยู่เสมอ ก็แทบที่จะทำให้วิธีการนี้ ใช้ป้องกันจริงๆไม่ได้

    ซึ่งก็จะขอเสนอเป็นบทความต่อไปครับ การใช้ fail2ban เพื่อป้องกันการโจมตีแบบ Brute Force DNS Quert Attack

  • สร้าง log สำหรับ DNS Query เมื่อใช้ bind9 เป็น DNS Server

    เนื่องจากปัญหาของ DNS Query Brute Force Attack ที่เกิดขึ้นกับ DNS Server บางตัวภายในเครือข่ายของมหาวิทยาลัย สงขลานครินทร์ วิธีการแรกที่ควรจะเอามาใช้ในการที่จะแก้ปัญหานี้ก็คือ “identify your enemy”.

    ใครคือคนที่ส่งคำสั่ง query เข้ามา? query อะไร? และ query บ่อยขนาดใหน?
    หลังจากแยกแยะข้อมูลในส่วนนี้ได้แล้ว เราค่อยตัดสินใจกันต่อว่าจะทำอย่างไรต่อไป

    สำหรับบทความนี้ จะพูดถึง Bind9 เท่านั้น Bind version ต่ำกว่า 9 ยังไม่ได้ทดสอบว่าใช้งานได้หรือเปล่า ส่วน version สูงกว่า 9, ถ้ามีก็น่าจะใช้งานได้ ส่วน DNS Server ตัวอื่นๆ ก็ยังไม่ได้ใช้อย่างจริงจังก็เลยไม่รู้เหมือนกันครับว่าจะจัดการอย่างไร แต่โดยหลักๆแล้วก็เหมือนกันครับ ก็คือ

    สร้าง log แล้วตรวจสอบจาก log

    สำหรับ bind9 ในส่วนของ option configuration จะมี keyword “logging” อยู่ ซึ่งสามารถใช้ระบุการ log ข้อมูลแบบต่างๆได้หลายอย่าง และ หลายรูปแบบมาก ในที่นี้ สมมติ เราสนใจเฉพาะส่วนของการ query นะครับ ก็อาจจะสร้าง config ได้แบบนี้

    logging {
          channel query_logging {
                 file “/var/log/named/query.log”;
                 print-time yes;
          };
          category queries { query_logging; };
    };

    เป็นการสร้าง channel สำหรับ log ขึ้นมาชื่อว่า “query_logging” โดยข้อมูลที่ถูกส่งมาที่ channel นี้จะเก็บลงไว้ในไฟล์ที่ชื่อว่า query.log ซึ่งไฟล์นี้จะเก็บอยู่ใน directory ที่ชื่อว่า /var/log/named

    config ข้างต้นจะเก็บเอาไว้ในไฟล์ /etc/bind/named.conf.options (สำหรับ Debian หรือ Ubuntu)
    โดยเพิ่มต่อท้ายเข้าไป

    ในส่วนของ directory ที่ใช้ในการเก็บ log (/var/log/named) ถ้ายังไม่มีอยู่ ก็ต้องสร้างขึ้นมาใหม่ก่อน และกำหนดให้เป็นของ user ซึ่ง run ตัว named daemon ซึ่งสำหรับบน Debian/Ubuntu จะใช้ user ที่ชื่อว่า bind ดังนั้น สามารถสร้าง diectory ขึ้นมาใช้งานได้โดยใช้คำสั่งดังนี้

    $ sudo mkdir -p /var/log/named
    $ sudo chown bind:bind /var/log/named

    หลังจากแก้ไขไฟล์ /etc/bind/named.conf.options แล้ว เราก็ควรที่จะตรวจสอบก่อนว่า config ไฟล์ที่แก้ไขไปยังอยู่ใน format ที่ถูกต้องก่อนที่จะใช้งาน ซึ่งทำได้โดยการใช้คำสั่ง

    $ sudo named-checkconf /etc/bind/named.conf

    ซึ่งถ้าไม่มีข้อความใดๆแสดงออกมา ก็แสดงว่า config ถูกต้อง เราก็สามารถสั่งให้ named อ่าน config ใหม่เข้าไปใช้ได้ โดยการใช้คำสั่ง

    $ sudo service bind9 reload

    หรือ

    $ sudo rndc reconfig

    หลังจากนั้น ก็สามารถตรวจสอบการทำงานของตัว named server ได้โดยใช้คำสั่ง

    $ host www.mydomain.net localhost

    เปลี่ยน ‘www.mydomain.net’ เป็น host ที่ตัว name server ของเราให้บริการอยู่
    หลังจากนั้นก็ไปตรวจสอบดูว่าในไฟล์ /var/log/named/query.log มีข้อมูลเพิ่มขึ้นมาหรือไม่

    ซึ่งถ้าการ config ทุกอย่างถูกต้อง ก็ควรที่จะมีข้อมูลของการ query เกิดขึ้น
    ตัวอย่างของข้อมูลที่ตัว named จะ log เอาไว้ครับ

    28-Nov-2012 11:54:14.911 client 174.127.97.86#25345: query: isc.org IN ANY +ED (172.30.0.85)
    28-Nov-2012 11:54:15.589 client 174.127.97.86#25345: query: isc.org IN ANY +ED (172.30.0.85)

    ซึ่งในที่นี้ client ที่ส่งคำสั่ง query มาก็คือ 174.127.97.86 โดย server ก็คือ 172.30.0.85 และข้อมูลที่ query ก็คือ isc.org ชนิดของข้อมูลเป็น any (ข้อมูลทั้งหมดเท่าที่มี)

    ในสภาวะที่ตัว Server ของเราถูกโจมตี ขนาดของ query.log ก็จะเพิ่มขึ้นอย่างรวดเร็วมาก
    จากตัวอย่างข้างต้น query ทั้งสองครั้งเกิดขึ้นในช่วงเวลาน้อยกว่า 1 วินาที (ครั้งแรกเวลา 11:54:14, ครั้งถัดมา 11:54:15 ตัวเลข 3 ตัวหลังจุด จะเป็นส่วนย่อยของวินาที)

    เราจะรู้ได้อย่างไรว่า client ตัวใหนบ้างที่กำลังโจมตี server ของเราอยู่ ตัวใหนที่ใช้งานแบบปกติ?
    เราสามารถใช้เครื่องมือบน linux/unix ในการช่วยตรวจสอบได้ครับ โดยการนับ

    $ cat /var/log/named/query.log | cut -f4 -d’ ‘ | cut -f1 -d’#’ | sort | uniq -c | sort -rn | head -20

    ตัวอย่าง output ที่ผมได้จาก server ที่ถูกโจมตีคือ

      18174 178.32.76.101
      10924 64.120.228.188
       5297 194.8.75.57
       4584 37.77.82.161
       1744 72.20.55.30
       1548 88.232.24.58
       1356 216.119.157.90
       1242 69.64.58.160
       1189 5.47.41.41
       1155 128.204.201.22
       1115 5.135.60.245
        872 31.214.144.181
        629 37.59.93.86
        513 88.228.212.44
        467 94.121.242.57
        438 178.32.239.65
        382 37.59.194.200
        343 174.127.92.85
        325 174.127.103.234
        296 88.245.53.247

    ตัวเลขข้างหน้าคือจำนวนครั้ง และตัวเลขข้างหลังคือ ip address ของ client ที่ส่งคำสั่ง query เข้ามาทั้งหมดนี้ query ช้อมูลตัวเดียวกันก็คือ “isc.org” type ANY

    เรารู้ตัวว่าผู้โจมตีเป็นใครแล้ว หลังจากนี้ก็จะเป็นคำถามว่า แล้วจะแก้ปัญหาอย่างไร
    ขอยกไปเป็นบทความหน้าครับ