คู่มือเกมแลค › ต้นเหตุของการส่งซ้ำใน TCP
mapping ของ NAT/โหลดบาลานเซอร์หมดอายุกลางการเชื่อมต่อ NAT / load balancer mapping expired mid-connection
ID สาเหตุ rt-mapping · ผู้รับผิดชอบหลัก พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) · ร่วมกับ พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), อินฟราเครือข่าย (ทีมอินฟรา), อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา)
เปิดการ์ดในฉบับหลักที่มีภาพและการทดลอง →
ถ้าอุปกรณ์กลางทางลบ mapping (รายการที่บันทึกว่าจะส่งต่อการเชื่อมต่อนี้ไปที่ไหน) ของการเชื่อมต่อที่ idle แพ็กเก็ตถัดไปที่ส่งจะไปไม่ถึง การเชื่อมต่อจะส่งซ้ำวนไปจนหลุด หรืออุปกรณ์ตอบกลับด้วยการปฏิเสธการเชื่อมต่อ (RST) แล้วหลุดทันที
ทำไม การเชื่อมต่อที่ไม่มีแพ็กเก็ตวิ่งอยู่พักหนึ่ง (AFK, อยู่ในล็อบบี้) → ผลคือ NAT ของเราเตอร์, CGNAT ของ ISP, ไฟร์วอลล์, โหลดบาลานเซอร์ หรือ security group บนคลาวด์ลบ mapping ที่ idle → บนหน้าจอ พอกลับมาขยับ การส่งซ้ำต่อเนื่องจนหลุด หรือหลุดทันที
- อาการ
- หลุด, ค้าง
- ปัจจัย
- แพ็กเก็ตหาย
- ใครเจอ
- เราคนเดียว, บางพื้นที่/บาง ISP
- เกิดเมื่อไร
- หลังอยู่เฉย ๆ สักพัก
- ผู้รับผิดชอบ
- ผู้รับผิดชอบหลัก พัฒนาไคลเอนต์ (ทีมพัฒนาเกม) · ร่วมกับ พัฒนาเซิร์ฟเวอร์ (ทีมพัฒนาเกม), อินฟราเครือข่าย (ทีมอินฟรา), อินฟราเซิร์ฟเวอร์ (ทีมอินฟรา)
- งานฝั่งทีมพัฒนาเกม
- ไคลเอนต์: ส่ง heartbeat โดยเว้นช่วงไม่เกินครึ่งหนึ่งของ idle timeout ที่สั้นที่สุด (mapping ในเราเตอร์ของผู้เล่นและ CGNAT ของ ISP จะต่ออายุแน่นอนก็ต่อเมื่อมีแพ็กเก็ตขาออกจากฝั่งใน และเราเปลี่ยน timeout เหล่านั้นไม่ได้ ไคลเอนต์จึงต้องเป็นฝ่ายส่ง), เชื่อมต่อใหม่อัตโนมัติเมื่อหลุด เซิร์ฟเวอร์: ตอบ heartbeat และถ้าไม่ได้รับนานเกินกำหนดให้ปิดการเชื่อมต่อเองก่อน (ลดช่วง TCP keepalive ด้วย socket option เช่น TCP_KEEPIDLE, ตรวจจับให้เร็วด้วย TCP_USER_TIMEOUT), ใช้ session token เพื่อต่อเซสชันเดิม
- งานฝั่งทีมอินฟรา
- เครือข่าย: รวบรวม idle timeout ของไฟร์วอลล์และโหลดบาลานเซอร์บนเส้นทางแล้วแชร์ให้ทีมพัฒนาเกม, เพิ่มค่าในไฟร์วอลล์และโหลดบาลานเซอร์ของเราถ้าจำเป็น เครื่องเซิร์ฟเวอร์/OS: ตรวจเวลา connection tracking ของ security group บนคลาวด์แล้วแชร์ให้ทีมพัฒนาเกม
- ตัวเลขที่ควรรู้
- เวลาที่คง TCP mapping ไว้ต่างกันไปตามอุปกรณ์ ตั้งแต่ไม่กี่นาทีถึงหลายชั่วโมง ถ้า security group บนคลาวด์ตั้งให้ติดตามการเชื่อมต่อ อินสแตนซ์ประเภท AWS Nitro v6 จะลบรายการที่ติดตามหลัง 350 วินาทีโดยค่าเริ่มต้น (ประเภทอื่น 5 วัน ดูรายการ “connection tracking ของ security group บนคลาวด์หมดอายุ”) ค่าเริ่มต้นของ TCP keepalive ใน Linux คือ “ตรวจเมื่อ idle ครบ 2 ชั่วโมง” จึงช้ากว่าอุปกรณ์ส่วนใหญ่
- บนกราฟ
- การเชื่อมต่อหลุดพร้อมกัน · จำนวนครั้งที่หลุด, ระยะเวลา idle ก่อนหลุด
- จุดที่ต้องดู
- packet capture ฝั่งเซิร์ฟเวอร์ช่วงไม่กี่นาทีสุดท้ายก่อนการเชื่อมต่อหลุด การเชื่อมต่อที่ยังอยู่ดูเวลา idle จาก lastsnd และ lastrcv (ms ที่ผ่านไปนับจากส่งและรับครั้งล่าสุด) ของ ss -ti และดู TcpExtTCPAbortOnTimeout (จำนวนการเชื่อมต่อที่ยกเลิกเพราะตัวจับเวลาหมด) ใน nstat ด้วย
- สัญญาณว่าใช่
- การเชื่อมต่อที่หลุดทุกรายการมีเวลา idle ก่อนหลุดเกินค่าใกล้เคียงกัน (idle timeout ของอุปกรณ์บนเส้นทาง เช่น 350 วินาทีของ security group บนอินสแตนซ์ AWS Nitro v6) และตั้งแต่แพ็กเก็ตแรกหลัง idle มีแต่การส่งซ้ำโดยไม่มี ACK จนยกเลิก หรือได้ RST กลับมาทันที
- สัญญาณว่าไม่ใช่
- หลุดระหว่างเล่นด้วยโดยไม่เกี่ยวกับเวลา idle: เป็นสาเหตุอื่น (“เส้นทางเปลี่ยน/เส้นทาง ECMP ที่เสีย”, “ไฟร์วอลล์/connection tracking ทิ้งแพ็กเก็ต”) ถ้าการเชื่อมต่อมี heartbeat วิ่งโดยเว้นช่วงไม่เกินครึ่งหนึ่งของ idle timeout ที่สั้นที่สุด ให้ตัดสาเหตุนี้ออก
- วิธีตรวจ
- ใช้เครื่องมือฝั่งอินฟรา (ไม่ต้องใช้โค้ดเกม)
แหล่งอ้างอิง
- RFC 5382: NAT Behavioral Requirements for TCP IETF
คำแนะนำว่า idle timeout ของการเชื่อมต่อใน TCP NAT ต้องไม่น้อยกว่า 2 ชั่วโมง 4 นาที (ตั้งอยู่บนสมมติฐานว่าอุปกรณ์อาจลบเซสชันที่ idle ไปก่อน) - RFC 4787: Network Address Translation (NAT) Behavioral Requirements for Unicast UDP IETF
NAT mapping ต้องต่ออายุด้วยแพ็กเก็ตขาออกจากฝั่งใน (REQ-6) ส่วนการต่ออายุด้วยแพ็กเก็ตขาเข้าจากฝั่งนอกเป็นทางเลือก (สำหรับ UDP) - Amazon EC2 security group connection tracking AWS
ค่าเริ่มต้นของ TCP idle tracking timeout คือ 350 วินาทีสำหรับอินสแตนซ์ประเภท Nitro v6 และ 432,000 วินาที (5 วัน) สำหรับประเภทอื่น แนะนำ keepalive ที่ถี่กว่า 5 นาที - IP Sysctl Linux kernel
tcp_keepalive_time ค่าเริ่มต้น 2 ชั่วโมง - tcp(7) — Linux manual page Linux man-pages
TCP_KEEPIDLE (เวลา idle ก่อนเริ่ม keepalive), TCP_USER_TIMEOUT (เวลาที่รอข้อมูลที่ยังไม่ได้รับการยืนยันก่อนปิดการเชื่อมต่อ) - RFC 5482: TCP User Timeout Option IETF
TCP user timeout: ข้อมูลที่ส่งไปยังไม่ได้รับการยืนยันนานเท่าไรจึงจะปิดการเชื่อมต่อ - ss(8) — Linux manual page iproute2
lastsnd และ lastrcv ใน ss -i: เวลาที่ผ่านไปนับจากส่งและรับครั้งล่าสุด (ms) - SNMP counter Linux kernel
TcpExtTCPAbortOnTimeout: จำนวนการเชื่อมต่อที่ยกเลิกไปโดยไม่ส่ง RST เพราะตัวจับเวลาของ TCP หมด
สาเหตุที่ควรดูประกอบ
ชั้นเดียวกัน: ต้นเหตุของการส่งซ้ำใน TCP
สาเหตุจากชั้นอื่นที่ทำให้เกิดอาการเดียวกัน (หลุด)
ดูการ์ดในฉบับหลักที่มีภาพและการทดลอง