วิธีแก้ปัญหา WireGuard ทำงานช้า ตรวจสอบสาเหตุที่แท้จริง
ปัญหา WireGuard ทำงานช้าส่วนใหญ่มักเกิดจากค่า MTU ไม่เหมาะสม เรียนรู้วิธีหาค่า Path MTU ด้วยการทำ Bisection การตั้งค่า TCP MSS Clamp และตรวจสอบทรัพยากร CPU เพื่อแก้ปัญหาให้ตรงจุด
สาเหตุที่แท้จริงของความเร็ว WireGuard ที่ช้า
ความเร็วที่ช้าของ WireGuard มีสาเหตุมาจากหนึ่งในสี่ปัจจัย ซึ่งแต่ละปัจจัยมีโอกาสเกิดขึ้นไม่เท่ากัน ประการแรกคือ MTU (maximum transmission unit): อุโมงค์สร้างแพ็กเก็ตที่มีขนาดใหญ่เกินกว่าที่ลิงก์ใดลิงก์หนึ่งบนเส้นทางจะรองรับได้ ส่งผลให้การถ่ายโอนข้อมูลขนาดใหญ่หยุดชะงักในขณะที่การรับส่งข้อมูลขนาดเล็กยังดูเป็นปกติ ประการที่สองคือตัวเส้นทางเครือข่ายเอง ซึ่งเป็นข้อจำกัดอยู่แล้วก่อนที่จะมีการสร้างอุโมงค์ ประการที่สามคือ CPU บน VPS แบบใช้ทรัพยากรร่วมกันขนาดเล็ก ซึ่งการเข้ารหัสต้องแย่งชิงทรัพยากรกับ guest รายอื่นบนโฮสต์เดียวกัน ประการที่สี่คือการเชื่อมต่อของ peer เอง
ให้ตรวจสอบตามลำดับนี้ MTU มาเป็นอันดับแรกเพราะเป็นสาเหตุเดียวในรายการที่เกิดจากตัว WireGuard เอง และเนื่องจากอาการของมันไม่ได้ดูเหมือนความช้าแต่อย่างใด MTU ที่ไม่ถูกต้องมักแสดงอาการเป็นอุโมงค์ที่เชื่อมต่อได้ทันที ตอบสนองต่อ ping ได้ ยอมให้ล็อกอิน SSH ได้ แต่จะค้างทันทีเมื่อคุณเริ่มคัดลอกไฟล์
มีอาการหนึ่งที่ควรตัดออกก่อนเริ่มตรวจสอบทั้งหมด หากทุกเว็บไซต์ใหม่ใช้เวลาหลายวินาทีกว่าจะเริ่มโหลดแล้วจึงถ่ายโอนข้อมูลด้วยความเร็วเต็มที่ นั่นคือปัญหาการแก้ไขชื่อโดเมน (name resolution) ไม่ใช่ปัญหา throughput DNS over WireGuard มีรูปแบบความล้มเหลวเฉพาะตัว และไม่มีการปรับเปลี่ยน MTU ใดที่จะแก้ไขปัญหาเหล่านั้นได้
เหตุใด WireGuard จึงใช้ MTU 1420?
ทุกแพ็กเก็ตที่คุณส่งเข้าสู่ทันเนลจะถูกเข้ารหัสและห่อหุ้มไว้ภายในแพ็กเก็ตใหม่ ส่วนที่ห่อหุ้มนี้จะใช้พื้นที่เป็นไบต์ ซึ่งไบต์เหล่านั้นจะถูกหักออกจากส่วนข้อมูล (payload) ของคุณ
ส่วนหัวข้อมูล (data header) ของ WireGuard มีขนาด 32 ไบต์ ประกอบด้วยฟิลด์ประเภท 4 ไบต์, ดัชนีผู้รับ 4 ไบต์, ตัวนับ 8 ไบต์ และแท็กยืนยันตัวตน Poly1305 ขนาด 16 ไบต์ รอบนอกส่วนนี้คือส่วนหัว UDP ขนาด 8 ไบต์ และถัดออกไปอีกคือส่วนหัว IP ภายนอก ซึ่งมีขนาด 20 ไบต์สำหรับ IPv4 และ 40 ไบต์สำหรับ IPv6 ดังนั้นการห่อหุ้มทั้งหมดจึงมีขนาด 60 ไบต์เมื่อ Endpoint ของคุณเป็นที่อยู่ IPv4 และ 80 ไบต์เมื่อเป็น IPv6 หน้าโปรโตคอลของ WireGuard ได้บันทึกโครงสร้างข้อความที่มาของตัวเลขเหล่านี้ไว้
wg-quick ไม่ใช้วิธีการคาดเดาค่านี้ โดยจะอ่านค่า MTU ของอินเทอร์เฟซที่ใช้กำหนดเส้นทางไปยัง Endpoint ของคุณ จากนั้นจะลบออกด้วย 80 บนเส้นทาง Ethernet ขนาด 1500 ไบต์ทั่วไป ผลลัพธ์ที่ได้คือ 1420 ซึ่งเป็นค่าที่ ip link show wg0 แสดงออกมา การลบออกด้วย 80 แทนที่จะเป็น 60 เพื่อให้ค่าดังกล่าวยังคงปลอดภัยหากปลายทางนั้นถูกเข้าถึงผ่าน IPv6 ซึ่งมีส่วนหัวภายนอกใหญ่กว่า 20 ไบต์
The data behind this chart
[
{
"label": "Ethernet, IPv4 endpoint",
"path_mtu": 1500,
"encap_overhead": 60,
"usable_wg0_mtu": 1440
},
{
"label": "Ethernet, IPv6 endpoint",
"path_mtu": 1500,
"encap_overhead": 80,
"usable_wg0_mtu": 1420
},
{
"label": "PPPoE DSL, IPv4 endpoint",
"path_mtu": 1492,
"encap_overhead": 60,
"usable_wg0_mtu": 1432
},
{
"label": "Extra tunnel in the path",
"path_mtu": 1400,
"encap_overhead": 80,
"usable_wg0_mtu": 1320
}
]แถว 4 แถวนั้นเป็นผลจากการคำนวณทางคณิตศาสตร์ ไม่ใช่การวัดจริง บนเส้นทางขนาด 1500 ไบต์ที่ไม่มีปัญหาและมีปลายทางเป็น IPv4 ค่า 1440 จะสามารถใช้งานได้ ดังนั้นค่าเริ่มต้นที่ 1420 จึงเหลือพื้นที่ว่างไว้ 20 ไบต์ ส่วนต่างนี้เป็นความตั้งใจและไม่ใช่ปัญหาของคุณ
แถวสุดท้ายคือส่วนที่ส่งผลกระทบ เมื่อลิงก์บางจุดบนเส้นทางรองรับได้เพียง 1400 ไบต์ ทันเนลที่ยังคงตั้งค่าไว้ที่ 1420 จะสร้างแพ็กเก็ตภายนอกขนาด 1500 ไบต์สำหรับทุกเซกเมนต์ที่มีขนาดเต็ม ซึ่งใหญ่กว่าที่ลิงก์นั้นจะยอมรับได้ถึง 100 ไบต์ ค่าที่เหมาะสมคือ 1320
อย่าคัดลอกค่า 1320 ไปใช้โดยตรง ค่า MTU ของเส้นทางเป็นคุณสมบัติเฉพาะของเส้นทางที่คุณใช้งาน และวิธีเดียวที่จะทราบค่าที่ถูกต้องคือการวัดค่าด้วยตนเอง
ลักษณะของปัญหา MTU ที่ไม่ถูกต้อง
ความล้มเหลวนี้ไม่ได้เกิดขึ้นแบบค่อยเป็นค่อยไป แต่เป็นการแยกส่วนที่ชัดเจนระหว่างแพ็กเก็ตขนาดเล็กและขนาดใหญ่
pingผ่านอุโมงค์ทำงานได้ที่ขนาดปกติทุกขนาด- การล็อกอิน SSH ทำงานได้สำเร็จและพิมพ์โต้ตอบได้รวดเร็ว
curl -I https://example.comส่งคืนส่วนหัว (headers) ได้ทันทีcurl https://example.comบนหน้าเว็บขนาดใหญ่จะค้างหลังจากโหลดไปได้เพียงไม่กี่กิโลไบต์scpของไฟล์ขนาดใหญ่จะเริ่มทำงานแล้วหยุดลงที่เปอร์เซ็นต์ใดเปอร์เซ็นต์หนึ่ง- เซสชัน SSH จะค้างทันทีที่คุณรันคำสั่งที่แสดงผลลัพธ์จำนวนมาก
เหตุการณ์ทั้งหมดนี้เกิดขึ้นเพราะการเชื่อมต่อ TCP จะสร้างเซกเมนต์ขนาดเต็มก็ต่อเมื่อมีข้อมูลจำนวนมากที่ต้องส่งเท่านั้น การทำ handshake และคำขอแรกจะมีขนาดเล็กพอที่จะผ่าน MTU ทุกจุดบนเส้นทางได้ การหยุดชะงักจะเริ่มขึ้นที่เซกเมนต์ขนาดเต็มเซกเมนต์แรก ซึ่งเป็นเหตุผลว่าทำไมการเชื่อมต่อจึงดูปกติจนกระทั่งถึงจุดที่ใช้งานไม่ได้
แพ็กเก็ตภายนอกที่มีขนาดใหญ่กว่า MTU ของลิงก์ถัดไปจะพบกับชะตากรรมอย่างใดอย่างหนึ่งในสองอย่างนี้
มันถูกแบ่งส่วน (Fragmented) เราเตอร์จะแยกแพ็กเก็ตออกเป็นส่วนๆ และปลายทางจะประกอบชิ้นส่วนเหล่านั้นกลับเข้าด้วยกัน การถ่ายโอนข้อมูลจะทำงานได้แต่ช้าลง เพราะคุณต้องเสียค่าใช้จ่ายเป็นสองแพ็กเก็ตสำหรับข้อมูลหนึ่งชุด และผู้รับต้องเก็บสถานะไว้จนกว่าชิ้นส่วนทั้งหมดจะมาถึง การสูญเสียชิ้นส่วนใดชิ้นส่วนหนึ่งไปจะทำให้แพ็กเก็ตต้นฉบับทั้งหมดสูญหาย ดังนั้นเส้นทางที่มีการสูญเสียข้อมูล 1% จะมีพฤติกรรมแย่กว่าเส้นทางปกติมาก ไฟร์วอลล์หลายแห่งยังปฏิเสธ IP fragments ตามนโยบาย ซึ่งจะเปลี่ยนผลลัพธ์นี้ให้กลายเป็นกรณีถัดไป
มันถูกทิ้ง (Dropped) โดยที่คุณอาจได้รับแจ้งหรือไม่ก็ได้ เราเตอร์ที่ไม่ยอมแบ่งส่วนแพ็กเก็ตจะส่งข้อความ ICMP (internet control message protocol) ประเภท "fragmentation needed" กลับไปยังผู้ส่ง โดยระบุค่า MTU ที่มันยอมรับได้ หากข้อความนั้นมาถึง การค้นหา Path MTU จะทำงานและผู้ส่งจะลดขนาดเซกเมนต์ลงโดยอัตโนมัติ เครือข่ายจำนวนมากกรอง ICMP ทิ้ง ทำให้ข้อความมักจะมาไม่ถึง และไม่มีการรายงานการสูญเสียข้อมูลอื่นใด นั่นคือหลุมดำ (black hole): แพ็กเก็ตถูกส่งออกไป ไม่มีอะไรตอบกลับมา ไม่มีข้อผิดพลาดปรากฏใน log ใดๆ ที่ปลายทางทั้งสองฝั่ง และการถ่ายโอนข้อมูลจะค้างจนกว่าจะมีบางอย่างหมดเวลา (timeout)
ฉันจะหาค่า MTU ที่เหมาะสมได้อย่างไร
ให้วัดขนาดของเส้นทาง (path) แล้วทำการลบออก ทดสอบที่ชั้น underlay แทนที่จะทดสอบที่ตัว tunnel โดยให้ ping ไปยัง public address ของเซิร์ฟเวอร์จากฝั่ง client และห้ามไม่ให้มีการทำ fragmentation
ping -M do -s 1472 -c 3 203.0.113.10-M do เป็นการตั้งค่าบิต DF (don't fragment) เพื่อไม่ให้ router ระหว่างทางทำการแบ่งแพ็กเก็ต -s คือขนาดของ ICMP payload แพ็กเก็ต IPv4 ที่สมบูรณ์จะมีขนาดเท่ากับ payload นั้นบวกด้วย 8 ไบต์ของ ICMP header และ 20 ไบต์ของ IP header ดังนั้น -s 1472 จึงเป็นการส่งข้อมูลขนาด 1500 ไบต์ลงบนสายสัญญาณพอดี
ผลลัพธ์ที่สำคัญมีอยู่ 3 รูปแบบ หากได้รับคำตอบปกติแสดงว่าขนาด 1500 ไบต์ผ่านได้และ MTU ไม่ใช่ปัญหาของคุณ หากได้รับข้อผิดพลาดจากเครื่องตัวเอง แสดงว่า interface ของคุณมีขนาดเล็กกว่าค่าที่คุณระบุไว้ตั้งแต่ต้น:
ping: local error: message too long, mtu=1500หากได้รับคำตอบจาก router ระหว่างทาง นั่นคือคำตอบที่คุณต้องการและสามารถหยุดการทดสอบได้ทันที:
From 203.0.113.1 icmp_seq=1 Frag needed and DF set (mtu = 1492)หากแพ็กเก็ตสูญหาย 100% ที่ขนาด 1472 แต่ได้รับคำตอบปกติที่ขนาดเล็กลง แสดงว่าเป็นกรณี black hole ซึ่งไม่มี router ตัวใดแจ้งข้อมูลกลับมา ให้หาค่าขอบเขตโดยการแบ่งครึ่ง (halving) โดยเก็บค่าที่ทราบว่าใช้งานได้และค่าที่ทราบว่าใช้งานไม่ได้ไว้ แล้วทดสอบที่จุดกึ่งกลาง จากนั้นขยับขอบเขตตามผลลัพธ์ที่ได้ การทำเช่นนี้ในแต่ละรอบจะลดช่วงที่เหลือลงครึ่งหนึ่ง ดังนั้นการทำ 5 หรือ 6 รอบก็เพียงพอแล้ว
ตัวอย่างการแบ่งครึ่งช่วง (bisection) ทีละรอบ
แต่ละบรรทัดคือคำสั่งที่คุณรันบน client เพื่อทดสอบกับ public address ของเซิร์ฟเวอร์ ส่วนคอมเมนต์คือผลลัพธ์ที่ได้รับกลับมา
ping -M do -s 1472 -c 3 203.0.113.10 # 100% loss, so 1500 is too big
ping -M do -s 1272 -c 3 203.0.113.10 # replies, so 1300 fits
ping -M do -s 1372 -c 3 203.0.113.10 # replies, so 1400 fits
ping -M do -s 1422 -c 3 203.0.113.10 # 100% loss, so 1450 is too big
ping -M do -s 1397 -c 3 203.0.113.10 # 100% loss, so 1425 is too big
ping -M do -s 1384 -c 3 203.0.113.10 # 100% loss, so 1412 is too bigpayload ขนาดใหญ่ที่สุดที่ผ่านได้คือ 1372 ดังนั้นเส้นทางนี้รองรับข้อมูลอย่างน้อย 1400 ไบต์และน้อยกว่า 1412 ไบต์ ให้เลือกค่าที่ปลอดภัยไว้ก่อน โดยค่า path MTU ที่ 1400 ลบด้วย 80 ไบต์ของ encapsulation จะได้ค่า MTU สำหรับ wg0 เท่ากับ 1320
tracepath จะทำการค้นหาแบบเดียวกันนี้โดยอัตโนมัติ และควรลองรันก่อนที่จะเริ่มทำการแบ่งครึ่งช่วงด้วยตนเอง:
tracepath -n 203.0.113.10บรรทัดสุดท้ายของผลลัพธ์จะรายงานค่าที่พบ:
Resume: pmtu 1492 hops 12 back 12ให้ถือว่าเครื่องมือทั้งสองเป็นเพียงจุดเริ่มต้นไม่ใช่บทสรุป เนื่องจากโฮสต์บางแห่งอาจจำกัดอัตรา (rate limit) หรือบล็อก ICMP ทั้งหมด ทำให้การแบ่งครึ่งช่วงอาจรายงานค่า MTU ที่ต่ำกว่าความเป็นจริง การทดสอบด้วยการถ่ายโอนข้อมูลที่เคยล้มเหลวคือการทดสอบที่แท้จริง
ให้ลองปรับค่าแบบ live ก่อน เพราะหากคาดการณ์ผิด คุณสามารถย้อนกลับได้ด้วยคำสั่งเดียว:
sudo ip link set mtu 1320 dev wg0ลองทำการถ่ายโอนข้อมูลที่เคยค้างอีกครั้ง หากสำเร็จ ให้บันทึกค่าดังกล่าวลงในบล็อก [Interface] ของ client อย่างถาวร:
[Interface]
PrivateKey = <client private key>
Address = 10.8.0.2/32
MTU = 1320sudo wg-quick down wg0 && sudo wg-quick up wg0
ip link show wg0ip link show wg0 ควรแสดงค่า mtu 1320 หากยังแสดงค่าเดิมอยู่ แสดงว่า wg-quick ไม่ได้อ่านไฟล์ที่คุณแก้ไข ให้ตรวจสอบว่าคุณแก้ไขไฟล์ /etc/wireguard/wg0.conf และตรวจสอบว่า MTU อยู่ภายใต้ [Interface] ไม่ใช่ภายใต้ [Peer] ซึ่งจะถูกละเว้น
MTU เป็นคุณสมบัติของ interface แต่ละตัวและไม่มีการเจรจาระหว่าง peer การตั้งค่าที่ฝั่ง client จะลดขนาดแพ็กเก็ตที่ client ส่งออกไปเท่านั้น แต่เซิร์ฟเวอร์ยังคงสร้างแพ็กเก็ตด้วยค่า MTU ของ wg0 ของตัวเองอยู่ ดังนั้นการดาวน์โหลดอาจยังคงเกิดปัญหา black hole หลังจากที่การอัปโหลดใช้งานได้แล้ว คุณควรตั้งค่าให้เท่ากันทั้งสองฝั่ง หรือทำการ clamp MSS ที่ฝั่งเซิร์ฟเวอร์แทน
เหตุใด MSS clamping จึงแก้ไขได้เฉพาะ TCP เท่านั้น
หากเซิร์ฟเวอร์ทำหน้าที่ส่งต่อทราฟฟิกให้กับ peer ซึ่งเป็นสิ่งที่เกิดขึ้นใน การตั้งค่า WireGuard VPS มาตรฐาน ที่ใช้ NAT (network address translation) กฎเพียงข้อเดียวสามารถแก้ไขปัญหา TCP ให้กับทุก peer และช่วยให้คุณไม่ต้องคอยไล่ปรับค่าบนไคลเอนต์แต่ละเครื่องที่คุณไม่สามารถควบคุมได้
MSS (maximum segment size) คือออปชันของ TCP ที่แต่ละฝั่งจะระบุไว้ในแพ็กเก็ต SYN เพื่อแจ้งว่าตนเองสามารถรับเซกเมนต์ได้ใหญ่เพียงใด การทำ clamping จะเขียนออปชันนั้นใหม่ในระหว่างการส่งข้อมูลเพื่อให้ตรงกับ MTU จริงของเส้นทาง ดังนั้นทั้งสองฝั่งจึงตกลงใช้เซกเมนต์ที่เล็กลงก่อนที่จะมีการรับส่งข้อมูลใดๆ วิธีนี้ได้ผลเพราะเกิดขึ้นระหว่างการทำ handshake และไม่ขึ้นอยู่กับข้อความ ICMP ที่เส้นทางอาจกำลังกรองทิ้งอยู่
สำหรับ nftables ให้เพิ่มตารางนี้ลงใน /etc/nftables.conf ต่อจากตารางเดิมที่คุณมีอยู่:
table inet mangle {
chain forward {
type filter hook forward priority mangle; policy accept;
tcp flags syn tcp option maxseg size set rt mtu
}
}โหลดการตั้งค่าใหม่ด้วย sudo systemctl reload nftables สำหรับเครื่องที่ใช้ iptables คำสั่งที่เทียบเท่ากันคือบรรทัดเดียว:
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtuตรวจสอบว่ากฎดังกล่าวอยู่ในเส้นทางที่แพ็กเก็ตวิ่งผ่าน ให้รัน sudo nft list table inet mangle หรือ sudo iptables -t mangle -L FORWARD -n -v ในขณะที่ไคลเอนต์กำลังเปิดการเชื่อมต่อใหม่ แล้วสังเกตว่าตัวนับเพิ่มขึ้นหรือไม่ หากตัวนับค้างอยู่ที่ศูนย์ แสดงว่าแพ็กเก็ตไม่ได้วิ่งผ่าน hook นั้น กฎดังกล่าวจึงไม่มีผลใดๆ
ข้อจำกัดตามความเป็นจริงมีดังนี้ การทำ clamping ครอบคลุมเฉพาะ TCP เท่านั้น และครอบคลุมเฉพาะทราฟฟิกที่ถูกส่งต่อ (forwarded traffic) ดังนั้นบริการที่รันอยู่บนตัวเซิร์ฟเวอร์ WireGuard เองจะไม่วิ่งผ่าน forward hook จึงไม่ถูกทำ clamping นอกจากนี้ กฎจะมีผลกับเฉพาะการเชื่อมต่อที่เปิดขึ้นหลังจากโหลดกฎแล้วเท่านั้น ส่วนเซสชันที่มีอยู่เดิมจะยังคงใช้ค่า MSS ที่ตกลงกันไว้ก่อนหน้า
UDP จะไม่ได้รับผลกระทบเนื่องจาก UDP ไม่มี handshake ให้เขียนใหม่ อย่างไรก็ตามทราฟฟิก UDP ส่วนใหญ่ยังคงใช้งานได้ตามปกติ QUIC ซึ่งเป็นโปรโตคอลขนส่งภายใต้ HTTP/3 จะตรวจสอบขนาดแพ็กเก็ตที่ใช้งานได้ด้วยตนเองและเริ่มต้นด้วยขนาดเล็กโดยเจตนา สิ่งที่จะใช้งานไม่ได้คือ UDP ที่ส่ง datagram ขนาดใหญ่เพียงหนึ่งชุดและคาดหวังว่าจะผ่านไปได้ เช่น คำตอบของ DNSSEC (DNS security extensions) ที่มีขนาดเกิน 1400 ไบต์ คิวรีเหล่านั้นจะหมดเวลาและลองใหม่ผ่าน TCP ซึ่งจะทำให้ผู้ใช้รู้สึกว่าเว็บไซต์โหลดช้าแทนที่จะใช้งานไม่ได้เลย
CPU ของ VPS เป็นข้อจำกัดหรือไม่
WireGuard ใช้การเข้ารหัสแบบ ChaCha20-Poly1305 และแลกเปลี่ยนกุญแจด้วย Curve25519 ในเส้นทางการรับส่งข้อมูลไม่มีการใช้ AES เลย ซึ่งส่งผลให้เกิดความเข้าใจผิดที่พบบ่อยคือ ชุดคำสั่ง AES-NI ใน CPU ของคุณ ไม่ได้ช่วยเพิ่มประสิทธิภาพให้ WireGuard แต่อย่างใด โฮสต์ที่ระบุว่ารองรับ AES-NI ไม่ได้มอบฟีเจอร์เพิ่มประสิทธิภาพให้กับ WireGuard เหตุผลที่เลือกใช้ ChaCha20 เพราะมีความเร็วสูงในการประมวลผลด้วยซอฟต์แวร์ทั่วไป แม้บน CPU ที่ไม่มีตัวเร่งความเร็วการเข้ารหัสลับก็ตาม
อย่างไรก็ตาม WireGuard ไม่ได้ทำงานโดยไม่ใช้ทรัพยากร บน VPS ที่มี 1 vCPU คอร์นั้นจะต้องรับผิดชอบทั้งการเข้ารหัสและการจัดการ network interrupts นอกเหนือไปจากงานของแอปพลิเคชันที่คุณรันอยู่
ให้วัดค่าในขณะที่มีการรับส่งข้อมูล:
sudo apt install -y sysstat
mpstat -P ALL 1ให้อ่านค่าจากสามคอลัมน์ %soft คือเวลาของ softirq ซึ่งเป็นส่วนที่ kernel ใช้ประมวลผลแพ็กเก็ต %steal คือเวลาที่ hypervisor จัดสรรให้กับผู้อื่น %idle คือเวลาที่เหลืออยู่สำหรับคุณ
หาก %soft มีค่าใกล้ 100 บนคอร์เดียวที่คุณมี แสดงว่าเซิร์ฟเวอร์ถึงขีดจำกัดในการประมวลผลแพ็กเก็ตแล้ว ซึ่งเป็นข้อจำกัดที่แท้จริงที่การเพิ่มจำนวนคอร์จะช่วยแก้ไขได้ top จะแสดง ksoftirqd/0 อยู่ที่ด้านบนสุดของรายการ process ในช่วงเวลาเดียวกัน ซึ่งเป็นการยืนยันผลลัพธ์เดียวกันจากมุมมองที่ต่างออกไป
หาก %steal มีค่าสูงกว่าไม่กี่เปอร์เซ็นต์ แสดงว่าข้อจำกัดนี้อยู่นอกเหนือการควบคุมของคุณ เนื่องจากโฮสต์มีการใช้งานเกินขีดความสามารถ (oversubscribed) และ vCPU ของคุณกำลังรอคิวเพื่อใช้งาน physical core ปัญหานี้พบได้บ่อยในแผนบริการแบบ shared ที่ราคาถูกที่สุดและค่านี้จะเปลี่ยนแปลงไปตามช่วงเวลาของวัน Steal time จาก noisy neighbour จำเป็นต้องมีการตรวจสอบแยกต่างหาก และการปรับค่า MTU จะไม่ช่วยแก้ไขปัญหานี้
อีกปัจจัยหนึ่งคือ implementation ที่ฝั่งไคลเอนต์ใช้งาน Linux kernel module เป็นเส้นทางที่เร็วที่สุดและสามารถกระจายภาระการเข้ารหัสของ peer ไปยังคอร์ต่างๆ ได้ ส่วน wireguard-go ซึ่งเป็น implementation ในระดับ userspace จะมีความเร็วต่ำกว่า และเป็นสิ่งที่ไคลเอนต์บน macOS และ iOS ใช้งาน เนื่องจากแพลตฟอร์มเหล่านั้นไม่อนุญาตให้แอปโหลด kernel module เข้าไปทำงานได้
เป็นที่เส้นทางหรือเป็นที่ลิงก์ของฝั่งตรงข้ามกันแน่?
ก่อนจะปรับแต่งค่าใดๆ ให้วัดค่าสองชุดจากไคลเอนต์เดียวกันโดยทิ้งช่วงห่างกันเพียงไม่กี่นาที ได้แก่ ปริมาณการรับส่งข้อมูล (throughput) แบบไม่ผ่านอุโมงค์ และแบบที่ผ่านอุโมงค์ หากไม่มีข้อมูลทั้งสองชุดนี้ คุณก็ทำได้เพียงแค่เดาเท่านั้น
รัน iperf3 -s บนเซิร์ฟเวอร์ การทดสอบโดยตรงจำเป็นต้องเปิดพอร์ต TCP 5201 บน IP สาธารณะ ดังนั้นให้เปิดพอร์ตไว้เฉพาะช่วงเวลาที่ทดสอบและลบกฎนั้นออกหลังจากเสร็จสิ้น ตรวจสอบให้แน่ใจว่าพอร์ตถูกปิดอีกครั้ง แทนที่จะคาดเดาเอาเอง
# on the client, outside the tunnel
iperf3 -c 203.0.113.10
# then through the tunnel
iperf3 -c 10.8.0.1หากตัวเลขทั้งสองใกล้เคียงกัน แสดงว่า WireGuard ใช้ทรัพยากรน้อยมากและข้อจำกัดอยู่ที่เส้นทางเครือข่าย แต่หากตัวเลขของอุโมงค์ต่ำกว่าการทดสอบโดยตรงมากในขณะที่ %soft ยังคงต่ำอยู่ ให้กลับไปตรวจสอบค่า MTU การแตกตัวของแพ็กเก็ต (fragmentation) จะลดปริมาณการรับส่งข้อมูลโดยไม่ทำให้ระบบหยุดทำงาน จึงปรากฏให้เห็นเป็นเปอร์เซ็นต์ที่สูญเสียไปแทนที่จะเป็นการค้าง
ให้ทดสอบทั้งสองทิศทางเนื่องจากอินเทอร์เน็ตตามบ้านมักมีความเร็วไม่สมมาตร (asymmetric) คำสั่ง iperf3 -c 10.8.0.1 -R จะสลับทิศทางการไหลของข้อมูลเพื่อให้เซิร์ฟเวอร์เป็นฝ่ายส่ง ไคลเอนต์ที่ใช้เน็ตความเร็ว 500/20 จะไม่สามารถส่งข้อมูลเข้าอุโมงค์ได้เกิน 20 Mbit ตามความเร็วอัปโหลดของตนเอง และไม่มีการตั้งค่าฝั่งเซิร์ฟเวอร์ใดที่จะเปลี่ยนข้อจำกัดนี้ได้
จากนั้นให้ทดสอบด้วยการส่งข้อมูลแบบขนาน (parallel streams):
iperf3 -c 10.8.0.1 -P 4หากการส่งข้อมูลสี่สตรีมพร้อมกันทำความเร็วได้มากกว่าสตรีมเดียว แสดงว่าการเชื่อมต่อ TCP เพียงชุดเดียวไม่สามารถใช้ความจุของเส้นทางได้เต็มที่ ปริมาณการรับส่งข้อมูลของหนึ่งสตรีมถูกจำกัดด้วยขนาดหน้าต่างรับข้อมูล (receive window) หารด้วยเวลาไปกลับ (round trip time) ดังนั้นเส้นทางที่มีค่า latency 150 ms จึงต้องการหน้าต่างขนาดใหญ่เพื่อส่งข้อมูลจำนวนมาก ตรวจสอบข้อจำกัดของคุณเองได้ดังนี้:
sysctl net.ipv4.tcp_rmem net.ipv4.tcp_wmemค่าที่สามในแต่ละรายการคือค่าสูงสุดที่ Linux จะปรับจูนอัตโนมัติ การสูญเสียแพ็กเก็ต (packet loss) จะจำกัดความเร็วของสตรีมเดียวอย่างรุนแรง เพราะการสูญเสียคือสิ่งที่กลไกควบคุมความแออัดของ TCP ตอบสนอง และเส้นทางที่ยาวทำให้การกู้คืนข้อมูลมีต้นทุนสูง ให้รัน mtr -rwc 100 203.0.113.10 จากฝั่งไคลเอนต์เป็นเวลาหนึ่งร้อยรอบเพื่อดูว่าการสูญเสียเกิดขึ้นที่จุดใดตลอดเส้นทาง การสูญเสียที่เริ่มจาก hop หนึ่งและต่อเนื่องไปจนถึง hop สุดท้ายคือการสูญเสียจริง ส่วนการสูญเสียที่ hop กลางแล้วหายไปหลังจากนั้นเกิดจากเราเตอร์ตัวนั้นลดลำดับความสำคัญของ ICMP ซึ่งไม่มีนัยสำคัญใดๆ
เพื่อให้ได้ภาพรวมของเซิร์ฟเวอร์ที่ทำซ้ำได้และแยกออกจากปัจจัยด้านเครือข่าย ให้ทำ benchmark VPS ด้วยวิธีที่เป็นมาตรฐาน เพื่อให้คุณสามารถรันการทดสอบเดิมซ้ำหลังจากทำการเปลี่ยนแปลงและเปรียบเทียบผลลัพธ์ได้อย่างถูกต้อง
สิ่งที่ WireGuard แก้ไขไม่ได้
WireGuard เป็นเพียงอุโมงค์เชื่อมต่อ (tunnel) มันไม่สามารถทำงานได้เร็วกว่าลิงก์ที่ช้าที่สุดในเส้นทางที่ใช้ และการเพิ่มอุโมงค์เข้าไปจะทำให้เส้นทางช้าลงเล็กน้อยเสมอ
มันไม่มีการบีบอัดข้อมูล ไม่มีฟังก์ชันที่เทียบเท่ากับ comp-lzo ของ OpenVPN และไม่มีแผนที่จะเพิ่มฟังก์ชันนี้ เนื่องจากกระบวนการบีบอัดข้อมูลก่อนการเข้ารหัสจะทำให้ข้อมูลเกี่ยวกับข้อความต้นฉบับ (plaintext) รั่วไหล ข้อมูลส่วนใหญ่ในปัจจุบันถูกบีบอัดมาอยู่แล้ว ดังนั้นในทางปฏิบัติคุณจึงไม่เสียประโยชน์ใดๆ นี่คือหนึ่งในความแตกต่างที่แท้จริงที่ควรพิจารณาเมื่อเปรียบเทียบ WireGuard กับ OpenVPN และเป็นการตัดสินใจเชิงออกแบบที่ตั้งใจไว้
การทำ full tunnel จะเปลี่ยนเส้นทางของทุกแพ็กเก็ตที่ส่งออกไป ทราฟฟิกที่เคยวิ่งจากคุณไปยัง CDN (content delivery network) ที่อยู่ใกล้เคียง จะเปลี่ยนไปวิ่งจากคุณไปยัง VPS ของคุณก่อนแล้วจึงต่อไปยัง CDN หาก VPS อยู่คนละทวีป ทุกคำขอจะต้องเสียเวลาอ้อมและค่า round trip time จะเพิ่มขึ้นตามระยะทาง ไม่มีค่าคอนฟิกใดที่จะทำให้เส้นทางสั้นลงได้ คุณต้องย้าย VPS ให้ใกล้ขึ้น หรือใช้ split tunnel เพื่อให้เฉพาะทราฟฟิกที่จำเป็นต้องใช้ VPN เท่านั้นที่วิ่งผ่านเส้นทางไกล ทราฟฟิกใดจะไปทางไหนนั้นถูกตัดสินใจโดย AllowedIPs ทั้งสิ้น และ cryptokey routing จะอธิบายว่าการตัดสินใจนั้นเกิดขึ้นได้อย่างไร
ข้อจำกัดของผู้ให้บริการอยู่นอกเหนืออุโมงค์และมักถูกมองข้าม แผนบริการที่มีการจำกัดปริมาณข้อมูลต่อเดือนมักจะบีบความเร็วพอร์ตให้ต่ำลงมากเมื่อใช้ครบโควตา ซึ่งจะทำให้ดูเหมือนอุโมงค์มีปัญหา ให้ตรวจสอบแผงควบคุมของคุณก่อนที่จะเสียเวลาทั้งคืนไปกับการปรับแต่ง MTU
PersistentKeepalive ไม่มีผลต่อปริมาณการรับส่งข้อมูล (throughput) มันมีไว้เพื่อรักษา NAT mapping ให้เปิดอยู่เพื่อให้เซิร์ฟเวอร์ยังคงติดต่อไคลเอนต์ที่อยู่หลังเราเตอร์ตามบ้านได้ การลดค่านี้ให้ต่ำกว่า 25 วินาทีจะเพิ่มจำนวนแพ็กเก็ตโดยเปล่าประโยชน์และไม่ได้ช่วยแก้ไขปัญหาใดๆ
ลำดับขั้นตอนการตรวจสอบ
- จำลองปัญหาที่เกิดขึ้นและระบุว่าเป็นอาการค้าง (hang) หรือเพียงแค่การทำงานช้าลง (slowdown) หากระบบค้างมักมีสาเหตุมาจาก MTU แต่หากทำงานช้าลงอย่างสม่ำเสมออาจเกิดจากสาเหตุอื่น
- ทำการ bisect ด้วย
ping -M doจากฝั่งไคลเอนต์ไปยัง public address ของเซิร์ฟเวอร์ แล้วจดบันทึกค่า path MTU ไว้ - ลบค่าออก 80 แล้วตั้งค่า MTU นั้นบน wg0 ที่ปลายทางทั้งสองฝั่ง จากนั้นทดสอบการโอนถ่ายข้อมูลที่เคยล้มเหลวอีกครั้ง
- เพิ่มการทำ MSS clamping บนเซิร์ฟเวอร์หากมีการส่งต่อ (forward) ทราฟฟิกให้กับ peer
- รัน
mpstat -P ALL 1ในระหว่างการโอนถ่ายข้อมูลและอ่านค่าจาก%softและ%steal - รัน
iperf3ทั้งภายนอกและภายในอุโมงค์ (tunnel) โดยทดสอบทั้งสองทิศทาง ทั้งแบบ single stream และแบบใช้-P 4 - รัน
mtr -rwc 100ไปยังเซิร์ฟเวอร์และตรวจสอบการสูญหายของแพ็กเก็ต (loss) ที่ยังคงปรากฏจนถึง hop สุดท้าย
ให้เปลี่ยนแผนการใช้งานเซิร์ฟเวอร์ (server plan) ก็ต่อเมื่อขั้นตอนที่ 5 ระบุว่า CPU คือข้อจำกัดสูงสุดเท่านั้น ขั้นตอนที่ 1 ถึง 4 ไม่มีค่าใช้จ่ายและสามารถแก้ไขรายงานปัญหาอุโมงค์ทำงานช้าได้เกือบทั้งหมด
FAQ
ทำไม WireGuard tunnel ถึง ping ได้เร็วแต่ดาวน์โหลดช้า?
อาการนี้เป็นสัญญาณบ่งบอกถึงปัญหา MTU โดยทั่วไป packet ขนาดเล็กจะผ่านทุก link ในเส้นทางได้ ทำให้ ping และการล็อกอินผ่าน SSH ทำงานได้ปกติ แต่การโอนถ่ายข้อมูลจำนวนมากจะส่ง segment ขนาดเต็ม ซึ่งเมื่อถูกห่อหุ้ม (encapsulated) แล้วจะมีขนาดใหญ่เกินกว่าที่บาง link จะยอมรับได้ หาก router ปลายทางทิ้ง packet เหล่านั้นโดยไม่ส่งข้อความ ICMP กลับมา จะไม่มีการรายงานการสูญหายและทำให้การโอนถ่ายข้อมูลค้าง ให้ค้นหา path MTU ด้วยการทำ ping -M do แบบ bisection ไปยัง public address ของเซิร์ฟเวอร์ จากนั้นลบออก 80 bytes สำหรับการห่อหุ้ม แล้วตั้งค่าผลลัพธ์ที่ได้เป็น MTU ของ wg0 ที่ปลายทางทั้งสองฝั่ง
ควรตั้งค่า MTU สำหรับ WireGuard เท่าใด?
ไม่มีตัวเลขที่เป็นมาตรฐานสากล ซึ่งเป็นเหตุผลว่าทำไมค่าเริ่มต้นที่ 1420 ถึงใช้งานไม่ได้สำหรับบางคน ค่า 1420 มาจาก 1500 ลบด้วย 80 bytes ของ WireGuard header, UDP header และ outer IPv6 header หากเส้นทางของคุณรองรับขนาดน้อยกว่า 1500 bytes ซึ่งเป็นเรื่องปกติสำหรับ PPPoE DSL และทุกที่ที่ traffic ของคุณต้องผ่าน tunnel อื่น คุณจำเป็นต้องใช้ค่าที่น้อยลง ให้วัดค่า path MTU ก่อน แล้วจึงลบออก 80
การทำ MSS clamping สามารถใช้แทนการตั้งค่า MTU ได้หรือไม่?
ไม่ได้ การทำ clamping จะเขียนทับตัวเลือก MSS ในขั้นตอน TCP handshake เพื่อให้ปลายทางทั้งสองฝั่งส่ง segment ที่มีขนาดเล็กลง ซึ่งช่วยแก้ปัญหา TCP ได้โดยไม่ต้องแตะต้อง interface แต่ UDP ไม่มี handshake ให้เขียนทับ จึงไม่ได้รับผลกระทบ นอกจากนี้ clamping ยังมีผลเฉพาะกับ traffic ที่เซิร์ฟเวอร์ส่งต่อ (forward) เท่านั้น ดังนั้นบริการที่รันอยู่บนตัวเซิร์ฟเวอร์ WireGuard เองจะไม่ได้รับประโยชน์ ควรใช้ทั้งสองวิธีควบคู่กัน คือตั้งค่า MTU บน interface ให้ถูกต้อง และใช้ clamping เพื่อจัดการกับ peer ที่คุณไม่สามารถควบคุมการตั้งค่าได้
การอัปเกรด VPS ให้เร็วขึ้นจะทำให้ WireGuard เร็วขึ้นหรือไม่?
จะเร็วขึ้นก็ต่อเมื่อ CPU เป็นคอขวด ซึ่งคำสั่งเดียวสามารถบอกคุณได้ ให้รัน mpstat -P ALL 1 ในขณะที่กำลังโอนถ่ายข้อมูล หาก %soft มีค่าใกล้ 100 บน core เดียวของคุณ แสดงว่าการประมวลผล packet คือข้อจำกัด และการเพิ่มจำนวน core จะช่วยเพิ่มประสิทธิภาพได้ หากค่า %steal สูง แสดงว่า host มีการใช้งานเกินขีดจำกัด (oversubscribed) ดังนั้นการเปลี่ยนแผนหรือเปลี่ยน host คือคำตอบ แต่หากทั้งสองค่าต่ำในขณะที่ tunnel ยังคงช้า แสดงว่า CPU ยังว่างอยู่และการอัปเกรดแผนก็ไม่ช่วยอะไร
ทำไม Mac ถึงช้ากว่า Linux client บนเครือข่ายเดียวกัน?
Linux client ใช้ WireGuard module ในระดับ kernel ซึ่งประมวลผล packet ใน kernel space และกระจายการเข้ารหัสของ peer ไปยัง CPU core ต่างๆ ได้ ส่วนแอปบน macOS และ iOS ใช้ wireguard-go ซึ่งเป็นการทำงานในระดับ userspace เนื่องจากแพลตฟอร์มเหล่านั้นไม่อนุญาตให้แอปโหลด kernel module ได้ การทำงานในระดับ userspace จำเป็นต้องคัดลอก packet แต่ละตัวไปมาระหว่าง kernel กับแอปพลิเคชัน ซึ่งการคัดลอกนี้ทำให้ throughput ลดลง ช่องว่างของประสิทธิภาพนี้เป็นเรื่องปกติ และไม่มีการตั้งค่าใดใน client ที่จะแก้ไขได้