SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor

อธิบายหลักการทำงาน WireGuard และกลไก Cryptokey Routing

ทำความเข้าใจการทำงานของ WireGuard ผ่านกลไก Cryptokey Routing และการตั้งค่า AllowedIPs ที่ทำหน้าที่เป็นทั้งตารางเส้นทางและรายการควบคุมการเข้าถึงในไฟล์ wg0.conf อย่างละเอียด

หลักการทำงานของ WireGuard ในประโยคเดียว

WireGuard ทำงานโดยการผูกแพ็กเก็ตทุกชุดเข้ากับ public key กลไกนี้มีชื่อเรียกว่า cryptokey routing และนี่คือหัวใจสำคัญของการออกแบบทั้งหมด: บรรทัด AllowedIPs ที่อยู่ถัดจาก peer คือตารางเส้นทาง (routing table) สำหรับแพ็กเก็ตที่ออกจากเครื่องของคุณ และเป็นรายการควบคุมการเข้าถึง (access control list) สำหรับแพ็กเก็ตที่ส่งมาจาก peer นั้น การตั้งค่าเพียงหนึ่งเดียวแต่ทำหน้าที่สองอย่าง หากคุณอ่าน AllowedIPs ด้วยวิธีนี้ ไฟล์คอนฟิกของ WireGuard ทุกไฟล์จะกลายเป็นเรื่องที่เข้าใจได้ง่าย

ไม่มีตารางเซสชันที่อ้างอิงด้วย IP address และไม่มีฐานข้อมูลผู้ใช้ Peer หนึ่งตัวประกอบด้วย public key และชุดของ address ที่ key นั้นได้รับอนุญาตให้ใช้งาน การทำ handshake และตัวจับเวลา (timers) มีไว้เพื่อรักษาความถูกต้องของการผูกข้อมูลนี้ในขณะที่เครือข่ายเบื้องล่างมีการเปลี่ยนแปลง หากคุณต้องการใช้งาน tunnel ให้ได้ผลก่อนที่จะศึกษาทฤษฎี ให้ลองสร้างขึ้นมาด้วย การทำ self-hosted WireGuard VPN บน VPS ของคุณเอง แล้วค่อยกลับมาที่นี่เมื่อคุณพบกับบรรทัดคอนฟิกที่น่าสงสัย

AllowedIPs คือตารางเส้นทางและรายการควบคุมการเข้าถึง

ให้พิจารณาขาออกก่อน เคอร์เนลของคุณจะส่งแพ็กเก็ตไปยังอุปกรณ์ wg0 ตามปกติผ่านตารางเส้นทางหลัก จากนั้น WireGuard จะนำที่อยู่ ปลายทาง ของแพ็กเก็ตนั้นไปเทียบกับตารางที่เก็บ prefix ที่อนุญาตของ peer ทุกตัว โดยใช้หลักการ prefix ที่ยาวที่สุดก่อน หากพบการจับคู่ ระบบจะระบุตัว peer ซึ่งจะระบุ public key และนำไปสู่ session key และ UDP endpoint แพ็กเก็ตจะถูกเข้ารหัสสำหรับ peer นั้นและส่งออกไป

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

ping: sendmsg: Required key not available

ข้อผิดพลาดนั้นหมายความว่า: ที่อยู่ที่คุณพยายามเข้าถึงไม่ได้ระบุไว้ภายใต้ peer ใดเลย ส่วนข้อผิดพลาดอื่นอย่าง ping: sendmsg: Destination address required หมายความว่ามีการจับคู่กับ peer ได้แล้ว แต่ WireGuard ไม่มี endpoint สำหรับ peer นั้น เนื่องจากไม่ได้มีการตั้งค่าไว้และยังไม่ได้รับข้อมูล endpoint มา

คราวนี้มาดูขาเข้าบ้าง แพ็กเก็ต UDP เดินทางมาถึงพอร์ตที่เปิดรับ WireGuard จะค้นหา session จาก receiver index ในส่วนหัว ตรวจสอบ counter เทียบกับ sliding replay window จากนั้นจึงถอดรหัสและตรวจสอบความถูกต้องของ payload หลังจากนั้นจึงจะอ่านแพ็กเก็ตภายใน และที่อยู่ ต้นทาง ของแพ็กเก็ตภายในนั้นจะต้องอยู่ใน AllowedIPs ของ peer ที่ส่งมา หากไม่อยู่ในเงื่อนไข แพ็กเก็ตจะถูกทิ้ง หากเปิดใช้งาน dynamic debug เคอร์เนลจะพิมพ์สาเหตุออกมาในบรรทัดลักษณะนี้:

wg0: Packet has unallowed src IP (10.8.0.9) from peer 2 (203.0.113.10:51820)

นี่คือเหตุผลที่ peer ฝั่งเซิร์ฟเวอร์ได้รับ /32 peer ที่ตั้งค่าด้วย AllowedIPs = 10.8.0.2/32 จะสามารถส่งแพ็กเก็ตจาก 10.8.0.2 ได้เท่านั้นและห้ามส่งจากที่อยู่อื่น หากคุณเขียน 0.0.0.0/0 ลงไปแทน ไคลเอนต์นั้นจะได้รับอนุญาตให้ส่งแพ็กเก็ตโดยอ้างที่อยู่ต้นทางใดก็ได้ภายในอุโมงค์ของคุณ รวมถึงที่อยู่ของไคลเอนต์อื่นด้วย

prefix ที่ซ้อนทับกันจะถูกแก้ไขด้วยความเฉพาะเจาะจง เนื่องจากเป็นการค้นหาแบบ longest prefix match หากมี prefix ที่เหมือนกันในสอง peer พฤติกรรมจะเป็นดังนี้: รายการจะย้ายไปอยู่กับ peer ที่ถูกตั้งค่าล่าสุด และ peer แรกจะหยุดรับทราฟฟิกนั้นโดยไม่มีข้อผิดพลาดแจ้งเตือนใดๆ wg show wg0 allowed-ips จะพิมพ์ตารางที่ใช้งานจริงในเคอร์เนล ซึ่งเป็นสิ่งที่สำคัญที่สุดในกรณีที่ไฟล์บนดิสก์และสถานะการทำงานจริงไม่ตรงกัน

การอ่านไฟล์คอนฟิกูเรชันโดยคำนึงถึงการกำหนดเส้นทางด้วย cryptokey

ฝั่งเซิร์ฟเวอร์:

[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <server private key>

[Peer]
PublicKey = <laptop public key>
AllowedIPs = 10.8.0.2/32

ฝั่งไคลเอนต์:

[Interface]
Address = 10.8.0.2/32
PrivateKey = <laptop private key>

[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

คีย์เวิร์ดเดียวกันนี้มีผลลัพธ์ตรงกันข้ามในทั้งสองฝั่ง ในฝั่งไคลเอนต์มันหมายถึง "ส่งทุกปลายทางไปยัง peer นี้" ในฝั่งเซิร์ฟเวอร์มันหมายถึง "ยอมรับเฉพาะที่อยู่นี้ที่มาจาก peer นี้เท่านั้น" ความไม่สมมาตรนี้อยู่ที่ค่าที่กำหนด ไม่ใช่ที่บทบาทของฝั่งใดฝั่งหนึ่ง

คีย์เหล่านี้ครึ่งหนึ่งไม่ใช่ส่วนหนึ่งของโปรโตคอล Address, DNS, MTU, PostUp และ SaveConfig เป็นของ wg-quick ซึ่งเป็นเชลล์สคริปต์ที่ทำหน้าที่เปิดใช้งานอินเทอร์เฟซ เคอร์เนลจะไม่เห็นคีย์เหล่านี้ wg-quick strip wg0 จะแสดงคอนฟิกูเรชันที่ลดทอนลงซึ่งเครื่องมือ wg โหลดใช้งานจริง ซึ่งเป็นวิธีที่เร็วที่สุดในการตรวจสอบการแยกส่วนนี้

สิ่งที่การทำ handshake ทำงานจริง

การทำ handshake ของ WireGuard คือ Noise_IKpsk2 ซึ่งมาจาก Noise Protocol Framework ส่วน IK คือส่วนที่เป็นประโยชน์สำหรับผู้ดูแลระบบ: public key แบบ static ของฝั่งตอบรับ (responder) เป็นสิ่งที่ฝั่งเริ่มต้น (initiator) ทราบอยู่แล้ว เนื่องจากเป็นค่า PublicKey ในบล็อก [Peer] ของคุณ และฝั่งเริ่มต้นจะส่ง public key แบบ static ของตนเองไปภายในข้อความแรกโดยมีการเข้ารหัสไว้ ดังนั้นจึงไม่มีการแลกเปลี่ยน certificate และไม่มีการรับส่งข้อมูลเพื่อยืนยันตัวตนเพิ่มเติม ผู้สังเกตการณ์ภายนอกจะไม่สามารถทราบได้ว่าคีย์ใดกำลังเรียกใช้งานอยู่ เว้นแต่จะถือครอง private key ของฝั่งตอบรับไว้

ต้นทุนที่ต้องแลกคือการรับส่งข้อมูลหนึ่งรอบ ข้อความเริ่มต้นมีขนาด 148 bytes ส่วนการตอบกลับมีขนาด 92 bytes และข้อมูลจะเริ่มไหลทันทีหลังจากนั้น แต่ละฝั่งจะสร้างคู่คีย์ Curve25519 แบบชั่วคราว (ephemeral) ขึ้นมาใหม่สำหรับการทำ handshake แต่ละครั้ง และ session key จะได้มาจากห่วงโซ่ของผลลัพธ์ Diffie-Hellman ซึ่งผสมผสานคีย์แบบ static และแบบชั่วคราวเข้าด้วยกัน จากนั้น private key แบบชั่วคราวจะถูกทำลายทิ้ง ซึ่งให้คุณสมบัติ forward secrecy: ผู้ที่บันทึกทราฟฟิกของคุณในวันนี้และขโมย private key ของเซิร์ฟเวอร์ไปในปีหน้า ก็ยังไม่สามารถอ่านข้อมูลที่บันทึกไว้ได้

การเริ่มต้น handshake จะแนบ timestamp แบบ TAI64N ไปด้วย และ peer แต่ละฝั่งจะจดจำค่า timestamp ล่าสุดที่ได้รับจากอีกฝั่งไว้ ดังนั้นการส่งซ้ำ (replay) ของข้อความเริ่มต้นจะถูกปฏิเสธ แพ็กเก็ตข้อมูลจะใช้ตัวนับขนาด 64-bit เป็น nonce และฝั่งรับจะเก็บ sliding window ของตัวนับที่เพิ่งได้รับไว้ ทำให้สามารถจัดการกับการส่งซ้ำและการเรียงลำดับที่ผิดพลาดได้โดยไม่ต้องมีสถานะการเชื่อมต่อแบบ TCP

Session key จะมีอายุการใช้งานไม่นาน และตัวจับเวลา (timer) จะถูกคอมไพล์ไว้ในตัวซอฟต์แวร์แทนที่จะให้ปรับแต่งได้

ChartWireGuard protocol timers, in seconds
The data behind this chart
[
  {
    "label": "REKEY_TIMEOUT",
    "seconds": 5,
    "notes": "resend a handshake initiation that got no answer"
  },
  {
    "label": "KEEPALIVE_TIMEOUT",
    "seconds": 10,
    "notes": "send a keepalive after receiving data and sending none back"
  },
  {
    "label": "REKEY_ATTEMPT_TIME",
    "seconds": 90,
    "notes": "give up on the handshake and report the peer as down"
  },
  {
    "label": "REKEY_AFTER_TIME",
    "seconds": 120,
    "notes": "sender begins a fresh handshake for a new session key"
  },
  {
    "label": "REJECT_AFTER_TIME",
    "seconds": 180,
    "notes": "the old session key is refused and traffic stops"
  }
]

ค่าเหล่านี้เป็นค่าคงที่จากข้อกำหนดของโปรโตคอล ไม่ใช่ค่าที่วัดผลได้ 5 ของค่าเหล่านี้เป็นตัวขับเคลื่อนวงจรชีวิตของเซสชันทั้งหมด หลังจากใช้งานไป 120 วินาที ฝั่งส่งจะเริ่มทำ handshake ใหม่ และหลังจาก 180 วินาที คีย์เก่าจะถูกปฏิเสธทันที ทำให้ทราฟฟิกหยุดลงจนกว่าการทำ handshake ครั้งใหม่จะเสร็จสิ้น การเริ่มต้นที่ไม่มีการตอบกลับจะถูกส่งซ้ำทุกๆ 5 วินาที และจะถูกยกเลิกหลังจากผ่านไป 90 วินาที นี่คือเหตุผลที่ wg show แสดงผล latest handshake เป็นอายุแบบสัมพัทธ์ และเหตุผลที่ tunnel ที่ใช้งานปกติและมีสถานะดีจะรักษาอายุนี้ให้มีค่าน้อยอยู่เสมอ หากอายุเพิ่มขึ้นในขณะที่คุณกำลังส่งข้อมูล แสดงว่าการทำ handshake กำลังล้มเหลว ไม่ใช่เพราะ tunnel กำลังว่างงาน

เหตุใด peer จึงไม่มีบทบาทเป็น client หรือ server

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

peer ที่มีการตั้งค่า endpoint ไว้สามารถเริ่มการทำ handshake ได้ ส่วน peer ที่ไม่มีจะรอจนกว่าจะทราบที่อยู่และพอร์ตของอีกฝั่งจากแพ็กเก็ตแรกที่ผ่านการตรวจสอบสิทธิ์อย่างถูกต้อง endpoint ที่เรียนรู้มานั้นจะถูกจัดเก็บไว้และอัปเดตทุกครั้งที่มีแพ็กเก็ตที่ถูกต้องส่งมาจากที่อยู่ใหม่ นี่คือวิธีการทำงานของ roaming: แล็ปท็อปที่ย้ายจาก Wi-Fi ไปยังเครือข่ายมือถือจะยังคงรักษา tunnel เดิมไว้ได้ เนื่องจาก session ถูกระบุด้วย key และ index ไม่ใช่ด้วย IP address ไม่มีการเชื่อมต่อใหม่เกิดขึ้น เพราะไม่มีสิ่งใดที่เคยเชื่อมต่อกันในความหมายของ TCP มาก่อน

กลไกเดียวกันนี้สร้างข้อเท็จจริงที่ควรทราบคือ: peer ที่มี public address จะถือครอง public IP ล่าสุดที่ทราบของอีกฝั่งเสมอ และ wg show จะแสดงค่าดังกล่าวออกมา

Fixed primitives และไม่มีการเจรจาต่อรอง

WireGuard ไม่มีรายการ ciphersuite โดยใช้ ChaCha20-Poly1305 สำหรับการเข้ารหัสแบบยืนยันความถูกต้อง (authenticated encryption), Curve25519 สำหรับการตกลงกุญแจ (key agreement), BLAKE2s สำหรับการทำ hashing และ HKDF สำหรับการสร้างกุญแจ (key derivation) ทุกการติดตั้งใช้งานชุดคำสั่งเหล่านี้เหมือนกันหมด จึงไม่มีขั้นตอนการเจรจาต่อรองให้ต้องประมวลผล และไม่มีช่องทางให้ลดระดับความปลอดภัยไปใช้ตัวเลือกที่อ่อนแอกว่า ข้อแลกเปลี่ยนนี้เป็นเรื่องจริง หาก primitive ตัวใดตัวหนึ่งถูกเจาะ การแก้ไขจะต้องทำผ่านการอัปเดตเวอร์ชันของโปรโตคอลใหม่ทั้งหมดและอัปเดตทั้งสองฝั่ง ไม่ใช่การเปลี่ยนค่าคอนฟิก การตัดสินใจเพียงจุดเดียวนี้ช่วยลดโค้ดส่วนใหญ่และลดรูปแบบความล้มเหลวที่มักพบในอุโมงค์ข้อมูลแบบ TLS ซึ่งเป็นประเด็นหลักของการเปรียบเทียบใน WireGuard เทียบกับ OpenVPN

เหตุใดพอร์ตจึงไม่ตอบสนองต่อเครื่องมือสแกน

ข้อความ handshake ทุกข้อความจะมีฟิลด์ที่เรียกว่า mac1 ซึ่งเป็น MAC (message authentication code) ที่คำนวณจากข้อความโดยใช้คีย์ที่ได้มาจาก public key แบบ static ของ ผู้ตอบ ผู้ส่งที่ไม่ทราบ public key ดังกล่าวจะไม่สามารถสร้าง mac1 ที่ถูกต้องได้ และผู้รับจะทิ้งแพ็กเก็ตนั้นไปโดยไม่มีการตอบกลับใดๆ ทั้งสิ้น ไม่มีการแจ้งข้อผิดพลาด ไม่มีการส่ง reset และไม่มีข้อความ ICMP

ผลลัพธ์ที่เห็นได้ชัดคือการสแกน UDP ที่ไม่ได้รับข้อมูลใดๆ กลับมา

sudo nmap -sU -p 51820 vpn.example.com

nmap จะรายงานผลเป็น open|filtered ซึ่งเป็นคำตอบเดียวกับที่ได้รับเมื่อพอร์ตถูกไฟร์วอลล์ทิ้งแพ็กเก็ตโดยไม่แจ้งเตือน พอร์ตจะมีพฤติกรรมเหมือนกันไม่ว่า WireGuard จะกำลังทำงานอยู่หรือไม่ก็ตาม อย่างน้อยก็สำหรับผู้ที่ไม่มี public key ของคุณอยู่ในครอบครอง

ฟิลด์ที่สองคือ mac2 ทำหน้าที่จัดการกับภาระงานจากการโจมตีแบบ denial of service เมื่อผู้รับอยู่ภายใต้ภาระงานหนัก ระบบจะตอบกลับการเริ่มต้นการเชื่อมต่อที่ถูกต้องด้วย cookie ขนาด 64 ไบต์ที่ผูกกับ source address ของผู้ส่ง และจะปฏิเสธการประมวลผล public key ที่ใช้ทรัพยากรสูงจนกว่าผู้ส่งจะส่ง cookie นั้นกลับมา วิธีนี้ช่วยยืนยันว่า source address เป็นของจริงก่อนที่จะเสีย CPU ไปกับการประมวลผล และกลไกนี้จะทำงานเฉพาะเมื่อระบบอยู่ภายใต้ภาระงานหนักเท่านั้น

เหตุใด 0.0.0.0/0 จึงเปลี่ยน peer ให้กลายเป็น default route ของคุณ

เนื่องจาก AllowedIPs คือตารางเส้นทาง (routing table) ค่า AllowedIPs = 0.0.0.0/0, ::/0 จึงอ้างสิทธิ์ปลายทางทุกแห่งสำหรับ peer นั้น ซึ่งเป็นการตั้งค่าแบบ full tunnel โดยสมบูรณ์

กลไกการกำหนดเส้นทางที่ทำให้สิ่งนี้ทำงานได้นั้นน่าสนใจยิ่งกว่าตัวบรรทัดคำสั่งเอง การตั้งค่า default route แบบปกติผ่าน wg0 จะทำให้เกิดการวนลูป (loop) เนื่องจากแพ็กเก็ต UDP ที่เข้ารหัสซึ่งบรรจุทราฟฟิกของคุณจะต้องออกจากเครื่องเช่นกัน และมันจะไปตรงกับ default route ของตัวมันเอง wg-quick หลีกเลี่ยงปัญหานี้ด้วยการใช้ policy routing โดยจะทำเครื่องหมาย (mark) แพ็กเก็ตขาออกของ WireGuard ด้วย fwmark, นำ default route ของอุโมงค์ไปไว้ในตารางเส้นทางแยกต่างหาก และเพิ่มกฎเพื่อให้เฉพาะทราฟฟิกที่ไม่ได้ถูกทำเครื่องหมายเท่านั้นที่เข้าถึงอุโมงค์ได้ เมื่อรันคำสั่ง ip rule show คุณจะเห็นผลลัพธ์ดังนี้:

32764:	from all lookup main suppress_prefixlength 0
32765:	not from all fwmark 0xca6c lookup 51820
32766:	from all lookup main

0xca6c คือ 51820 ในรูปแบบเลขฐานสิบหก และ 51820 ก็คือหมายเลขตารางเส้นทางเช่นกัน กฎ suppress_prefixlength 0 ทำให้ตารางหลัก (main table) ข้าม default route ของตัวมันเองไป เพื่อให้เส้นทางที่เฉพาะเจาะจง เช่น subnet ภายในเครื่องของคุณยังคงมีความสำคัญสูงสุด ในขณะที่ทราฟฟิกส่วนที่เหลือจะถูกส่งต่อไปยังตารางของอุโมงค์ การทำ split tunnel ไม่จำเป็นต้องใช้กลไกเหล่านี้ เนื่องจากรายการที่แคบกว่าอย่าง AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 จะกลายเป็นเส้นทางปกติในตารางหลัก

สิ่งหนึ่งที่ full tunnel ไม่ได้แก้ไขด้วยตัวมันเองคือการแปลงชื่อ (name resolution) เนื่องจากตัวแก้ไข (resolver) ที่ไคลเอนต์ของคุณได้รับมาจากเครือข่ายท้องถิ่นมักจะยังคงใช้งานอยู่และมีเส้นทางที่เฉพาะเจาะจงกว่า ซึ่งเป็นงานแยกต่างหากที่ครอบคลุมอยู่ใน DNS ที่รั่วไหลออกนอกอุโมงค์ WireGuard

จุดประสงค์ที่แท้จริงของ PersistentKeepalive

WireGuard จะไม่ส่งข้อมูลใดๆ หากไม่มีการรับส่งข้อมูลเกิดขึ้น ไม่มีการส่ง heartbeat ไม่มีการรีเฟรช session และไม่มีสัญญาณใดๆ บนสายสื่อสาร ความเงียบนี้ช่วยประหยัดพลังงานแบตเตอรี่และช่วยในกรณีการสแกนที่กล่าวไปข้างต้น แต่ในขณะเดียวกันก็ทำให้การตั้งค่าบางรูปแบบใช้งานไม่ได้

Peer ที่อยู่หลัง NAT (network address translation) หรือหลัง stateful firewall จะสามารถติดต่อได้จากภายนอกก็ต่อเมื่อมีการสร้าง mapping ไว้ในอุปกรณ์นั้นๆ ซึ่ง mapping ดังกล่าวจะถูกสร้างขึ้นโดยแพ็กเก็ตที่ส่งออกไป โดยทั่วไปอายุการใช้งานของ UDP mapping จะเริ่มต้นที่ประมาณ 30 วินาที เมื่อ mapping หมดอายุ แพ็กเก็ตที่มาจากฝั่งสาธารณะจะถูก middlebox ปฏิเสธ และ tunnel จะดูเหมือนใช้งานไม่ได้จนกว่า peer ที่อยู่หลัง NAT จะส่งข้อมูลบางอย่างออกมา PersistentKeepalive = 25 จะส่งแพ็กเก็ตเปล่าที่ผ่านการตรวจสอบสิทธิ์ทุกๆ 25 วินาที ซึ่งเป็นระยะเวลาที่สั้นกว่าอายุการใช้งานทั่วไปที่สั้นที่สุด ทำให้ mapping ยังคงเปิดอยู่เสมอ

ให้ตั้งค่านี้บน peer ที่อยู่หลัง NAT ส่วนเซิร์ฟเวอร์ที่มี public address และเปิดพอร์ต UDP ไว้ไม่จำเป็นต้องตั้งค่านี้ และการตั้งค่าบนเซิร์ฟเวอร์จะเพิ่มปริมาณ traffic โดยไม่จำเป็น อย่าสับสนกับ automatic keepalive ซึ่งจะทำงานหลังจาก 10 วินาทีที่ peer ได้รับข้อมูลและไม่มีข้อมูลของตนเองที่จะส่งกลับ ระบบนี้จะทำงานอยู่ตลอดเวลาและไม่สามารถกำหนดค่าได้

การกำหนดเส้นทาง LAN ผ่าน tunnel ไม่ใช่ฟีเจอร์ของ WireGuard

สมมติว่า peer B อยู่ในเครือข่ายภายในบ้าน 192.168.50.0/24 และ peer A ต้องการเชื่อมต่อไปยังเครือข่ายนั้น ระบบสองส่วนที่แยกจากกันต้องทำงานสอดคล้องกัน และมีเพียงส่วนเดียวเท่านั้นที่เป็น WireGuard

ส่วนของ WireGuard: เพิ่ม 192.168.50.0/24 ลงใน AllowedIPs ของ B บนเครื่อง A สิ่งนี้จะทำให้ A ส่งข้อมูลไปยัง prefix นั้นผ่าน B และทำให้ A ยอมรับแพ็กเก็ตที่มี source address เหล่านั้นจาก B หากไม่มีการตั้งค่านี้ cryptokey routing จะไม่มีคีย์สำหรับปลายทางและไม่มีสิทธิ์สำหรับต้นทาง

ส่วนของ kernel: บนเครื่อง B ค่า net.ipv4.ip_forward จะต้องเป็น 1 มิฉะนั้น kernel จะทิ้งแพ็กเก็ตที่ถอดรหัสแล้วทุกฉบับที่ไม่ได้จ่าหน้าถึงตัว B เอง นอกจากนี้ forward chain ของ firewall บน B จะต้องอนุญาตให้ทราฟฟิกผ่านได้ และโฮสต์ต่างๆ ใน LAN จำเป็นต้องมีเส้นทางกลับไปยัง 10.8.0.0/24 หรือ B จะต้องทำ source NAT เพื่อให้การตอบกลับส่งกลับผ่าน B

หน้าที่ของ WireGuard จะสิ้นสุดลงเมื่อส่งแพ็กเก็ตที่ถอดรหัสแล้วให้กับ kernel หลังจากนั้นทุกอย่างจะเป็นการ routing และ filtering ตามปกติของ Linux ซึ่งเป็นเหตุผลว่าทำไมความผิดพลาดนี้จึงปรากฏในตัวนับ nft list ruleset หรือใน ip -s link show wg0 ไม่ใช่ใน wg show หากคุณต้องการจัดการ peer ผ่านเว็บอินเทอร์เฟซ การ รัน wg-easy ใน Docker จะช่วยสร้างรายการ peer ให้คุณโดยอัตโนมัติ แม้ว่ากฎการส่งต่อข้อมูล (forwarding rules) จะยังคงเป็นหน้าที่ของโฮสต์ก็ตาม

เหตุผลที่ WireGuard ทำงานในระดับเคอร์เนล

wg0 เป็นไดรเวอร์อุปกรณ์เครือข่าย แพ็กเก็ตจะเดินทางผ่าน routing stack ตามปกติ ถูกเข้ารหัสในบริบทของ softirq และส่งออกผ่าน UDP socket โดยไม่ต้องข้ามไปประมวลผลใน userspace เลย นี่คือที่มาของประสิทธิภาพการรับส่งข้อมูลที่สูง และยังเป็นเหตุผลที่โมดูลนี้มีขนาดเพียงประมาณสี่พันบรรทัด ซึ่งเล็กพอที่จะตรวจสอบและรวมเข้ากับ mainline Linux 5.6 ได้ในเดือนมีนาคม 2020 ทั้ง Ubuntu 24.04 และ Debian 13 ต่างก็มาพร้อมกับโมดูลนี้ ดังนั้นจึงขาดเพียงแค่แพ็กเกจ wireguard-tools เท่านั้น

การเป็นอินเทอร์เฟซตามปกติมีผลในทางปฏิบัติ tcpdump -ni wg0 จะแสดงแพ็กเก็ตภายในที่เป็นข้อความธรรมดา (plaintext) ในขณะที่ tcpdump -ni eth0 udp port 51820 จะแสดงแพ็กเก็ตภายนอกที่ถูกเข้ารหัสแล้ว การเปรียบเทียบทั้งสองส่วนจะช่วยให้คุณทราบได้ทันทีว่าทิศทางใดที่เกิดปัญหา netfilter และการจัดการ traffic shaping จะปฏิบัติต่อ wg0 เหมือนกับลิงก์เครือข่ายอื่นๆ ในกรณีที่โมดูลเคอร์เนลไม่สามารถใช้งานได้ เช่น ในการทำ container virtualisation ที่ใช้เคอร์เนลร่วมกับโฮสต์ wireguard-go จะทำหน้าที่นำโปรโตคอลเดียวกันไปใช้งานในระดับ userspace ผ่านอุปกรณ์ TUN ซึ่งต้องแลกมาด้วยประสิทธิภาพที่ลดลงอย่างเห็นได้ชัด เนื่องจากทุกแพ็กเก็ตจะต้องข้ามขอบเขตของเคอร์เนลถึงสองครั้ง

สิ่งที่ WireGuard ไม่สามารถป้องกันคุณได้

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

  • ไม่สามารถปกปิดได้ว่าคุณกำลังใช้งาน WireGuard ข้อความ handshake มีขนาดคงที่ ไบต์แรกระบุประเภทข้อความ และใช้โปรโตคอล UDP ในการรับส่งข้อมูล ซึ่งการตรวจสอบแพ็กเก็ตเชิงลึก (Deep packet inspection) สามารถระบุได้โดยง่าย และเครือข่ายที่ไม่อนุญาตให้ใช้ VPN สามารถบล็อกการเชื่อมต่อนี้ได้ การทำ obfuscation ถูกตัดออกไปตามการออกแบบ
  • ไม่สามารถปกปิดปริมาณหรือจังหวะเวลาในการรับส่งข้อมูลได้ ข้อมูล payload จะถูก padding ให้มีขนาดเป็นพหุคูณของ 16 ไบต์เท่านั้น ดังนั้นผู้สังเกตการณ์ยังคงเห็นได้ว่าคุณส่งข้อมูลเมื่อใดและมีปริมาณโดยประมาณเท่าใด
  • มีการเก็บข้อมูล endpoint ล่าสุดไว้เสมอ ฝั่ง peer ที่มี public address จะจัดเก็บ IP สาธารณะปัจจุบันของอีกฝั่งไว้ และ wg show จะแสดงข้อมูลดังกล่าว เมื่อรวมกับ tunnel address ที่กำหนดไว้ตายตัวในไฟล์ config ข้อมูลนี้จึงเป็นตัวระบุตัวตนที่คงที่ซึ่งติดตามผู้ใช้ไปในทุกเครือข่าย หากเป็น VPS ของคุณเองถือว่าไม่มีปัญหา แต่ด้วยเหตุนี้บริการเชิงพาณิชย์จึงต้องเพิ่มเลเยอร์ซ้อนทับเหนือโปรโตคอลนี้อีกชั้นหนึ่ง
  • เป็นการยืนยันตัวตนด้วยกุญแจ (key) ไม่ใช่ตัวบุคคล ใครก็ตามที่ถือไฟล์ private key ก็คือ peer นั้นๆ ดังนั้นควรตั้งค่า /etc/wireguard ให้เป็นโหมด 700 และไฟล์กุญแจให้เป็น 600
  • ไม่มีรายการเพิกถอน (revocation list) และไม่มีวันหมดอายุ การเข้าถึงจะสิ้นสุดลงเมื่อคุณลบรายการ peer ออกจากทุกเซิร์ฟเวอร์ที่เก็บข้อมูลนั้นไว้ และกุญแจแบบ static จะคงอยู่จนกว่าคุณจะลบออก

สิ่งเหล่านี้ไม่ได้ทำให้ WireGuard อ่อนแอ แต่ทำให้มันมีขนาดเล็ก ซึ่งนั่นคือจุดประสงค์หลัก คือการยืนยันตัวตนและเข้ารหัสข้อมูล โดยปล่อยให้การจัดการตัวตนและการจัดสรรที่อยู่เป็นหน้าที่ของระบบที่คุณสร้างขึ้นเหนือโปรโตคอลนี้ เลเยอร์การประสานงานในลักษณะที่อธิบายไว้ใน การเปรียบเทียบ WireGuard กับ Tailscale มีไว้เพื่อเติมเต็มช่องว่างดังกล่าวโดยใช้ data plane เดียวกับที่คุณเพิ่งอ่านไปข้างต้น

FAQ

Cryptokey routing ใน WireGuard คืออะไร

Cryptokey routing คือกฎที่ผูกแพ็กเก็ตทุกชุดเข้ากับ public key โดย peer แต่ละรายการจะมีรายการ prefix ระบุไว้ใน AllowedIPs สำหรับขาออก WireGuard จะเลือก peer โดยจับคู่ปลายทางของแพ็กเก็ตกับรายการของ peer แต่ละตัว โดยเริ่มจาก prefix ที่ยาวที่สุดก่อน รายการนี้จึงทำหน้าที่เป็นตารางเส้นทาง (routing table) ส่วนขาเข้า เมื่อแพ็กเก็ตถูกถอดรหัสและตรวจสอบสิทธิ์แล้ว ที่อยู่ต้นทางภายในจะต้องตรงกับรายการของ peer ตัวนั้น มิฉะนั้นแพ็กเก็ตจะถูกทิ้ง รายการนี้จึงทำหน้าที่เป็นรายการควบคุมการเข้าถึง (access control list) ไปในตัว WireGuard จึงไม่มีการตั้งค่า routing หรือ firewall ภายในแยกต่างหาก เพราะรายการเดียวนี้ทำหน้าที่ทั้งสองอย่าง

ฉันจำเป็นต้องตั้งค่า PersistentKeepalive บน peer ทั้งสองฝั่งหรือไม่

ไม่จำเป็น ให้ตั้งค่าเฉพาะฝั่งที่อยู่หลัง NAT (network address translation) หรือหลัง stateful firewall ซึ่งมักจะเป็นฝั่ง client เนื่องจาก WireGuard จะไม่ส่งข้อมูลใดๆ ในขณะที่ไม่ได้ใช้งาน การแมปที่ช่วยให้ฝั่งตรงข้ามเข้าถึง peer นั้นได้จึงหมดอายุลง ซึ่งมักเกิดขึ้นภายในหนึ่งนาที ทำให้ tunnel ดูเหมือนใช้งานไม่ได้ในทิศทางนั้น PersistentKeepalive = 25 จะส่งแพ็กเก็ตเปล่าที่ผ่านการตรวจสอบสิทธิ์ทุกๆ 25 วินาทีเพื่อรักษาการแมปให้คงอยู่ ส่วน peer ที่มี public address และเปิดพอร์ต UDP ไว้แล้วไม่จำเป็นต้องตั้งค่านี้

ทำไมการ ping ผ่าน tunnel ถึงแจ้งว่า "Required key not available"

เพราะที่อยู่ปลายทางไม่ได้อยู่ใน AllowedIPs ของ peer ใดเลย ทำให้ cryptokey routing ไม่พบ key สำหรับเข้ารหัสแพ็กเก็ตนั้นและ kernel จึงปฏิเสธที่จะส่งข้อมูล ให้รันคำสั่ง wg show wg0 allowed-ips แล้วเปรียบเทียบผลลัพธ์กับที่อยู่ที่คุณกำลัง ping อยู่ สำหรับข้อผิดพลาดที่คล้ายกันอย่าง Destination address required นั้นเป็นปัญหาที่ต่างออกไป คือมีการจับคู่ peer ได้แล้ว แต่ WireGuard ไม่มี endpoint สำหรับ peer นั้น เนื่องจากไม่ได้มีการตั้งค่าไว้และยังไม่มีแพ็กเก็ตที่ผ่านการตรวจสอบสิทธิ์ส่งมาจาก peer ดังกล่าว

Firewall สามารถตรวจจับและบล็อก WireGuard ได้หรือไม่

ได้ WireGuard จะตรวจสอบสิทธิ์และเข้ารหัสทราฟฟิกของคุณโดยไม่มีความพยายามในการอำพรางตัว ข้อความ handshake มีขนาดคงที่ที่ 148 และ 92 ไบต์ โดยไบต์แรกของทุกข้อความจะระบุประเภทของมัน และใช้โปรโตคอล UDP ดังนั้นการตรวจสอบแพ็กเก็ตเชิงลึก (deep packet inspection) จึงสามารถระบุโปรโตคอลนี้ได้โดยง่าย เครือข่ายที่บล็อก UDP หรือตรวจสอบลายนิ้วมือของโปรโตคอลจะสามารถหยุดการทำงานของมันได้ การซ่อน tunnel ต้องอาศัยการห่อหุ้มด้วยวิธีอื่น ซึ่งเป็นเครื่องมือแยกต่างหาก ไม่ใช่การตั้งค่าภายใน WireGuard