NetKubeLab EN

Network Fundamental ข้ามเครือข่าย

การเลือกเส้นทาง — เกตเวย์จัดการต่ออย่างไร

สิบเก้าบทบนเว็บนี้พูดถึงเส้นทาง แต่ไม่มีบทไหนบอกว่าเราเตอร์ตัดสินใจอย่างไร บทนี้ตอบด้วยกฎข้อเดียวที่วัดได้จากเครื่องตัวเอง

· หมวด 4 · ข้ามเครือข่าย · 9 นาที

บทความ IP จบหัวข้อการเลือกทางออกด้วยประโยคที่ฟังดูสวยงาม

"และเพราะทุกเครื่องบนเส้นทางคิดแบบเดียวกัน แพ็กเก็ตจึงเดินทางไปได้ทั่วโลก โดยไม่มีใครสักคนรู้เส้นทางทั้งเส้น"

แล้วจบตรงนั้น ส่วนบทความ VLAN ก็ชี้กลับมาว่าการข้ามวง "เป็นงานของเราเตอร์เสมอ" และ IP เองก็บอกแค่ว่า "ส่งให้เกตเวย์แล้วให้มัน จัดการต่อ"

ลูกศรชี้กันเป็นวงแล้วตกพื้น ไม่มีบทไหนบอกว่าเกตเวย์จัดการต่ออย่างไร

บทนี้ตอบคำถามนั้น และคำตอบสั้นกว่าที่คนคิดมาก — มีกฎข้อเดียว ซึ่งวัดได้จาก เครื่องที่คุณนั่งอยู่ตอนนี้

ถ้ายังไม่เคยดู เริ่มตรงนี้

เครื่องทุกเครื่องมีตารางเส้นทางของตัวเอง ไม่ใช่แค่เราเตอร์

$ netstat -rn -f inet

ของเครื่องที่ใช้เขียนบทความนี้ ย่อเหลือแก่น

  Destination        Gateway          Netif
  default            192.168.1.1      en0
  127                127.0.0.1        lo0
  192.168.1          link#12          en0
  192.168.1.130      (link layer)     en0

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

$ route -n get 8.8.8.8

มันบอกว่า เคอร์เนลเลือกบรรทัดไหน สำหรับปลายทางที่ถาม

กฎข้อเดียว — prefix ที่ยาวที่สุดชนะ

RFC 1812 เดือนมิถุนายน 1995 ชื่อ Requirements for IP Version 4 Routers เขียนกฎนี้ไว้เป็นประโยคเดียว

"Routers must use the most specific matching route (the longest matching network prefix) when forwarding traffic."

ปลายทางสามแบบตรงกับบรรทัดในตารางคนละบรรทัด โดยบรรทัดที่ prefix ยาวที่สุดเป็นผู้ชนะเสมอ

ผมถามเครื่องด้วยปลายทางสามแบบที่ตั้งใจให้ตรงคนละบรรทัด

  destination       matched entry     next hop
  192.168.1.130     192.168.1.130     ส่งตรง
  192.168.1.200     192.168.1.0       ส่งตรง
  8.8.8.8           default           192.168.1.1

สามปลายทาง สามบรรทัด และเรียงตามความยาวของ prefix พอดี — /32 ชนะ /24 และ /24 ชนะ /0

เอกสารยังยกตัวอย่างเดียวกันไว้ด้วย ถ้าปลายทางคือ 10.144.2.5 และตารางมี 10.144.2.0/24, 10.144.0.0/16 และ 10.0.0.0/8 กฎนี้เก็บเฉพาะอันแรก "because its prefix length is longest"

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

เส้นทางในตารางมาจากไหน

เส้นทางในตารางมาจากสามที่ คือสายที่เสียบอยู่ การตั้งค่าด้วยมือ และโพรโทคอลที่เราเตอร์คุยกันเอง

มีสามทางเท่านั้น

หนึ่ง connected — เสียบสายแล้วตั้งเลขไอพีบนอินเทอร์เฟซ เส้นทางไปวงนั้น โผล่ขึ้นมาเอง ในตารางข้างบนคือบรรทัด 192.168.1 ที่ gateway เป็น link#12 แทนที่จะเป็นเลขไอพี — นั่นแปลว่า "ไม่ต้องส่งให้ใคร ส่งออกทางนี้ได้เลย"

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

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

เส้นทางค่าเริ่มต้น คือการยอมรับว่าไม่รู้

บรรทัด default คือ 0.0.0.0/0 ซึ่งเป็น prefix ที่สั้นที่สุดเท่าที่เป็นไปได้ มันจึงตรงกับทุกปลายทางในจักรวาล และแพ้ทุกบรรทัดที่เจาะจงกว่า

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

ความหมายจริงของมันคือ "ปลายทางนี้ฉันไม่รู้จัก ส่งให้คนที่น่าจะรู้มากกว่าฉัน"

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

ที่น่าสนใจคือตารางของเครื่องที่ใช้เขียนบทความนี้มี เส้นทางค่าเริ่มต้นสองเส้น

  default   192.168.1.1   en0
  default   link#20       bridge100

สองบรรทัดที่ prefix ยาวเท่ากัน กฎ longest match จึงตัดสินไม่ได้ ต้องใช้เกณฑ์ อื่นต่อ เช่นลำดับความสำคัญของอินเทอร์เฟซ ซึ่งเป็นจุดที่ระบบปฏิบัติการแต่ละตัว ทำไม่เหมือนกัน และเป็นสาเหตุของอาการ "บางทีออกเน็ตได้ บางทีไม่ได้" ที่หายาก มากบนเครื่องที่มีหลายอินเทอร์เฟซ

ข้ามวงจริง ๆ ต้องทำอะไรบ้าง

บทความ VLAN บอกว่าต้องมีอุปกรณ์ที่มีขาอยู่ในทั้งสองวง แต่ไม่ได้บอกว่ามันทำ อะไรตอนแพ็กเก็ตผ่าน

เราเตอร์ที่ต่อผ่านทรังก์เส้นเดียวแล้วมีอินเทอร์เฟซย่อยต่อ VLAN เทียบกับสวิตช์ชั้นสามที่มีอินเทอร์เฟซเสมือนอยู่ในตัว

มีสองรูปแบบที่ใช้กันจริง

router on a stick — เราเตอร์ต่อกับสวิตช์ด้วยสายเส้นเดียวที่เป็นทรังก์ แล้วสร้างอินเทอร์เฟซย่อยหนึ่งอันต่อหนึ่ง VLAN ทราฟฟิกข้าม VLAN จึงวิ่งขึ้นไป หาเราเตอร์แล้ววิ่งกลับลงมาบนสายเส้นเดิม — ซึ่งแปลว่าสายเส้นนั้นรับภาระสองเท่า

switched virtual interface — สวิตช์ชั้นสามมีอินเทอร์เฟซเสมือนอยู่ข้างใน ตัวมันเอง ทราฟฟิกจึงไม่ต้องออกไปไหน เร็วกว่ามากและเป็นวิธีที่ใช้กันเป็นหลัก ในปัจจุบัน

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

สิ่งที่เปลี่ยนจริงตอนส่งต่อ

นี่คือส่วนที่คนมักเข้าใจผิด — เราเตอร์ไม่ได้เปลี่ยนที่อยู่ IP

แพ็กเก็ตเดียวกันผ่านเราเตอร์สามตัว ที่อยู่ IP ไม่เปลี่ยนเลย แต่ที่อยู่ MAC เปลี่ยนทั้งคู่ทุกฮอป และ TTL ลดลงหนึ่งทุกครั้ง

ผมสร้างหัว IP ขึ้นมาแล้วจำลองการผ่านเราเตอร์สามตัว

  hop   TTL   IP checksum   src IP        dst IP
    0    64   0xa2a9        192.0.2.10    198.51.100.7
    1    63   0xa3a9        192.0.2.10    198.51.100.7
    2    62   0xa4a9        192.0.2.10    198.51.100.7
    3    61   0xa5a9        192.0.2.10    198.51.100.7

ที่อยู่ IP ไม่ขยับเลยสักไบต์ สิ่งที่เปลี่ยนคือ TTL ลดลงหนึ่ง และ checksum ของหัว IP ต้องคิดใหม่ตาม

สังเกตว่า checksum เพิ่มขึ้น 0x0100 พอดีทุกครั้ง ซึ่งไม่ใช่ความบังเอิญ — มันคือเหตุผลที่สูตรลัดในบทความ การตรวจจับข้อผิดพลาด มีอยู่ตั้งแต่แรก เพราะเราเตอร์ต้องแก้ checksum ทุกฮอปและอ่านทั้งแพ็กเก็ตใหม่ ไม่ไหว

ส่วนที่เปลี่ยนทั้งคู่ทุกฮอปคือ ที่อยู่ MAC — ต้นทางกลายเป็นของเราเตอร์ที่ กำลังส่ง ปลายทางกลายเป็นของอุปกรณ์ถัดไป

นั่นคือเหตุผลที่ต้องมี ARP ทำงานทุกฮอป และเป็นเหตุผลที่ traceroute เห็นชื่อเราเตอร์ทีละตัวได้

เครื่องที่ใช้เขียนบทความนี้ก็เป็นเราเตอร์

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

$ sysctl net.inet.ip.forwarding
  net.inet.ip.forwarding: 1

ค่าเป็น 1 แปลว่าแล็ปท็อปเครื่องนี้กำลังส่งต่อแพ็กเก็ตให้คนอื่นอยู่ตอนนี้ ซึ่งผมไม่ได้ตั้งใจเปิดเอง มันถูกเปิดโดยฟีเจอร์ที่สร้างอินเทอร์เฟซ bridge100 ขึ้นมา

ความต่างระหว่างเครื่องธรรมดากับเราเตอร์จึงมีแค่ค่านี้ค่าเดียว — ค่าเป็น 0 แปลว่าแพ็กเก็ตที่ไม่ได้ส่งถึงเราจะถูกทิ้ง ค่าเป็น 1 แปลว่ามันจะถูกส่งต่อ

เมื่อมันโกหก

"เราเตอร์รู้เส้นทางทั้งเส้น" ไม่รู้เลย มันรู้แค่ว่าฮอปถัดไปคือใคร ประโยค ที่บทความ IP ทิ้งไว้จึงเป็นจริงทุกคำ

"เส้นทางในตารางคือเส้นทางที่ใช้จริง" ใช้จริงเฉพาะบรรทัดที่ชนะ ซึ่งอาจไม่ใช่ บรรทัดที่คุณกำลังอ่านอยู่ ให้ถามด้วย route -n get แทนการเดาจากตาราง

"เพิ่มเส้นทางแล้วทราฟฟิกจะไปทางใหม่" ไปก็ต่อเมื่อ prefix ยาวกว่าบรรทัดเดิม การเพิ่มเส้นทางที่กว้างเท่ากันไม่ได้ทำอะไรเลย

"เราเตอร์เปลี่ยนที่อยู่ IP" ไม่เปลี่ยน สิ่งที่เปลี่ยนที่อยู่ IP คือ NAT ซึ่งเป็นคนละเรื่องและเป็นสิ่งที่เพิ่มเข้ามาทีหลัง

"ping ได้แปลว่าเส้นทางถูก" ping พิสูจน์ว่ามีเส้นทางไปและกลับ ไม่ได้พิสูจน์ ว่าเป็นเส้นทางที่คุณตั้งใจ และเส้นทางขากลับอาจไปคนละทางกับขาไปได้

ตัวอย่างจริงจากงานจริง

กรณีที่ 1 — ทราฟฟิกไปผิดทาง

สถานการณ์ ตั้งเส้นทางใหม่แล้ว แต่ทราฟฟิกยังไปทางเดิม

คำสั่ง ถามเคอร์เนลตรง ๆ แทนที่จะอ่านตารางแล้วเดา

$ route -n get 10.20.30.40

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

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

กรณีที่ 2 — ไปถึงแต่กลับไม่ได้

สถานการณ์ ส่งออกไปได้ แต่ปลายทางตอบกลับไม่ถึง

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

สิ่งที่ยังไม่ได้พิสูจน์ อาการเดียวกันเกิดจากไฟร์วอลล์ที่ปล่อยขาออกแต่บล็อก ขากลับได้ด้วย ตามที่บทความ ไฟร์วอลล์ อธิบาย ต้องแยก สองอย่างนี้ด้วยการดูตัวนับที่อุปกรณ์

กรณีที่ 3 — เครื่องมีหลายอินเทอร์เฟซแล้วสับสน

สถานการณ์ เครื่องต่อทั้งสายและ Wi-Fi หรือมี VPN อยู่ ทราฟฟิกออกทางที่ ไม่คาดคิด

อ่านอย่างไร ดูว่ามีเส้นทางค่าเริ่มต้นกี่เส้น อย่างเครื่องที่ใช้เขียน บทความนี้มีสองเส้น เมื่อ prefix ยาวเท่ากัน กฎ longest match ตัดสินไม่ได้ ต้องไปดูเกณฑ์รองซึ่งแต่ละระบบปฏิบัติการทำไม่เหมือนกัน อาการที่ได้คือ "บางทีได้ บางทีไม่ได้" ซึ่งหาสาเหตุยากมากถ้าไม่รู้ว่าต้องมองตรงนี้

สิ่งที่ยังไม่ได้พิสูจน์ การรู้ว่ามีหลายเส้นทางค่าเริ่มต้นบอกว่ามีความ กำกวม ไม่ได้บอกว่าระบบจะเลือกอันไหน ต้องทดสอบด้วย route -n get กับปลายทาง จริงที่สนใจ

เมื่อการเลือกเส้นทางในบ้านเราไม่พอ

ทุกอย่างในบทนี้อธิบายว่าเราเตอร์ใช้ตารางอย่างไร แต่ยังไม่ได้ตอบว่าตาราง ของอินเทอร์เน็ตทั้งใบมาจากไหน

เส้นทางค่าเริ่มต้นที่บ้านโยนของไปให้ผู้ให้บริการ ผู้ให้บริการโยนต่อไปอีก และ สุดท้ายมีใครสักคนที่ต้องรู้จริง ๆ ว่า prefix นั้นอยู่ที่ไหนในโลก

คนคนนั้นรู้เพราะมีคนอื่นบอก และไม่มีอะไรพิสูจน์ว่าคนที่บอกพูดความจริง — ซึ่งเป็นรูปแบบเดียวกับที่ ARP, DHCP และ STP เจอมาแล้วทั้งสามบท

นั่นคือ BGP และเป็นเรื่องของบทถัดไป

อ้างอิง

มาตรฐาน

  • RFC 1812 มิถุนายน 1995 Requirements for IP Version 4 Routers — ประโยคเรื่อง longest match ที่ยกมาอ้าง และอัลกอริทึมการเลือกเส้นทางพร้อมตัวอย่างในเอกสาร

วัดจากเครื่องที่ใช้เขียน

  • netstat -rn -f inet ตารางเส้นทางจริง รวมทั้งเส้นทางค่าเริ่มต้นสองเส้น
  • route -n get การทดลองสามปลายทางที่ตรงคนละบรรทัด
  • sysctl net.inet.ip.forwarding ค่าที่ทำให้เครื่องธรรมดากลายเป็นเราเตอร์

คำนวณเอง

  • ตาราง TTL กับ checksum ของหัว IP ทีละฮอป คำนวณจากรูปแบบหัว IP โดยตรง ที่อยู่ที่ใช้เป็นช่วงเอกสารตาม RFC 5737

ความเชื่อใจวัดจากเครื่องจริงคำนวณเอง

อ่านหน้านี้เป็นภาษาอังกฤษ