อาการสองอย่างปรากฏในลูป VpnService เดียวกันและถูกโทษว่าเป็นสิ่งเดียวกัน: ผู้อ่านที่ไม่กลับมาเลย และการเขียนที่ล้มเหลวด้วย ENOBUFS สิ่งเหล่านี้ไม่ใช่รูปแบบของบั๊กเดียว การอ่านที่ค้างคือความไม่ลงรอยเรื่องการควบคุมการไหลกับ peer; การเขียนที่ล้มเหลวคือแพ็กเก็ตที่อินเทอร์เฟซจะไม่รับ หากมองว่าเป็นปัญหาเดียวกัน คุณจะเสียเวลาหนึ่งวันไปกับการปรับบัฟเฟอร์ที่ไม่มีส่วนเกี่ยวข้องเลย การแบ่งด้านล่างคือวิธีที่ฉันตัดสินใจว่าจะวัดครึ่งไหนของลูปก่อน

ลูปเดียว จุดล้มเหลวอิสระสองจุด

เซอร์วิสของคุณอ่านแพ็กเก็ต IP จากตัวอธิบายไฟล์ TUN ตรวจสอบมัน แล้วเขียนคำตอบกลับไปยังตัวอธิบายเดียวกัน ทุกอย่างที่พังในเส้นทางนั้นจะปรากฏที่การเรียกใดการเรียกหนึ่งในสองครั้งนั้น จึงมีการรายงานทั้งสองว่าเป็นอุโมงค์ที่พัง การเรียกทั้งสองมีสัญญาต่างกัน การอ่านรอให้เคอร์เนลส่งแพ็กเก็ตให้คุณ และการเขียนส่งแพ็กเก็ตให้เคอร์เนลซึ่งมีสิทธิ์ปฏิเสธ

การอ่านเป็นครึ่งที่คิดง่ายกว่า การอ่านที่ไม่คืนอะไรเลยอาจกำลังรอทราฟฟิกที่ไม่เคยมาถึง หรือรอเพราะ peer หยุดส่ง

การอ่านที่ค้างคือความไม่ลงรอยเรื่องการควบคุมการไหล

การอ่านอุปกรณ์ TUN จะบล็อกจนกว่าจะมีแพ็กเก็ต นั่นคือพฤติกรรมที่ออกแบบไว้ และนั่นคือเหตุผลที่เธรดที่หยุดอยู่ภายใน read ดูปกติบนลิงก์ที่ไม่ได้ใช้งาน มันกลายเป็นการค้างเมื่อสแตก TCP ของคุณยังคงรับข้อมูลจากแอปในเครื่องแต่ไม่เคยประกาศหน้าต่างที่เพียงพอให้ peer ระยะไกลส่งเพิ่มได้ peer จะเติมหน้าต่างที่คุณประกาศไว้ รอ ACK และการอัปเดตหน้าต่าง แล้วหยุด จากนั้นผู้อ่านของคุณไม่มีอะไรจะคืน และแอปเห็นการเชื่อมต่อที่เปิดอยู่แต่เงียบ

สาเหตุทั่วไปคือสแตกที่เขียนลงซ็อกเก็ตโดยไม่ติดตามหน้าต่างที่ประกาศไว้ และอีกอันที่ลืมส่งการอัปเดตหน้าต่างหลังจากระบายบัฟเฟอร์แล้ว จากภายนอกทั้งสองดูเหมือนกัน: ไบต์ออกไป ไม่มีอะไรกลับมา ไม่มีข้อผิดพลาดถูกโยน การบล็อกเธรดอ่านบนการแยกวิเคราะห์ การแก้ DNS หรือ I/O ของดิสก์ให้รูปร่างอาการเดียวกันด้วยสาเหตุที่ไม่เกี่ยวข้อง ดังนั้นยืนยันว่าเธรดอยู่ภายใน read จริงก่อนโทษการควบคุมการไหล

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

ENOBUFS เป็นปัญหาเรื่องขนาด ไม่ใช่การขาดบัฟเฟอร์

การเขียนที่คืน ENOBUFS บนอินเทอร์เฟซ TUN มักปฏิเสธความยาวแพ็กเก็ต อินเทอร์เฟซมี MTU แพ็กเก็ตที่ใหญ่กว่านั้นเข้าแถวไม่ได้ และการลองซ้ำแพ็กเก็ตเดิมก็ล้มเหลวแบบเดิมทุกครั้ง ความแน่นอนนั้นคือเบาะแส การขาดบัฟเฟอร์จริงจะหายไปเมื่อแรงกดดันลดลง ในขณะที่การปฏิเสธเรื่องขนาดไม่สนใจว่าคุณรอนานเท่าใด

TCP มีกลไกสำหรับเรื่องนี้โดยเฉพาะ และนั่นคือขั้นตอนที่คนข้ามไป: การหนีบ MSS เมื่อคุณสังเคราะห์เซกเมนต์ในพื้นที่ผู้ใช้ คุณเป็นคนเลือกขนาดเพย์โหลด และการเลือกจาก MTU ของอินเทอร์เฟซในเครื่องแทน MTU ของอุโมงค์จะปล่อยเซกเมนต์ที่อุโมงค์บรรทุกไม่ได้ หนีบให้เหลือค่าที่เล็กกว่าระหว่างสองค่า การหนีบครอบคลุมเฉพาะ TCP ดังนั้นดาตาแกรม UDP ที่ใหญ่เกินไปจะต้องถูกแบ่งส่วนหรือทิ้งโดยโค้ดของคุณ

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

การวัดสามอย่างที่แยกกรณีออกจากกัน

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

What you seeWhere to look
Reader parked, peer send queue empty, last advertised window zeroWindow updates in your ACK path
Write fails on one length and succeeds on a smaller oneMSS clamp and tunnel MTU
Both directions look fine, app still sees no dataProtected socket and routing loop

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

ซ่อมท่อที่ทั้งสองเส้นทางพึ่งพา

ไม่มีอาการใดคุ้มค่าที่จะไล่ตามจนกว่าอินเทอร์เฟซจะถูกสร้างอย่างถูกต้อง การเรียก builder ที่สำคัญคือที่อยู่ เส้นทาง MTU และความยินยอม ตามลำดับนั้น:

Builder()
  .addAddress(10.8.0.2, 32)
  .addRoute(0.0.0.0, 0)
  .setMtu(1400)
  .establish()

ตั้ง MTU เป็นค่าจริงของอุโมงค์แทนที่จะปล่อยไว้ตามที่ตัวอย่างใช้ ทุกอย่างที่คุณเขียนภายหลังจะถูกจำกัดด้วยตัวเลขนั้น และความไม่ตรงกันระหว่าง MTU ที่คุณตั้งกับ MSS ที่คุณหนีบไว้เป็นวิธีที่พบบ่อยที่สุดในการนำข้อผิดพลาดการเขียนกลับมาหลังการแก้ไข จากนั้นปกป้องซ็อกเก็ตที่บรรทุกทราฟฟิกไปยังเซิร์ฟเวอร์ VPN เอง หากไม่มี protect แพ็กเก็ตเหล่านั้นจะถูกกำหนดเส้นทางกลับเข้าอุโมงค์ของคุณเองและการเชื่อมต่อจะไม่สมบูรณ์ ซึ่งดูเหมือนการอ่านที่ค้างแต่จริงๆ แล้วเป็นลูปการกำหนดเส้นทาง

ความยินยอมหลีกเลี่ยงไม่ได้ เซอร์วิสที่เริ่มโดยที่ prepare ไม่คืน null จะไม่ได้อินเทอร์เฟซ และ establish คืน null แทนที่จะโยน ดังนั้นตรวจสอบตัวอธิบายก่อนเริ่มลูป เอกสารอ้างอิง VpnService คือที่ที่ควรยืนยันสัญญาของ builder แทนการคัดลอกตัวอย่าง

ตรวจสอบทีละทิศทาง

ส่งเสียงสะท้อน UDP ผ่านอุโมงค์และดูความยาวแพ็กเก็ตก่อนแตะ TCP จากนั้นเปิดแฮนด์เชกและยืนยันว่าเซกเมนต์ยังอยู่ใต้ MSS ที่คุณคำนวณไว้ นับแพ็กเก็ตแทนการเชื่อมต่อ เพื่อให้การค้างปรากฏเป็นช่องว่างในตัวนับแทนซ็อกเก็ตที่ค้างอยู่ หากการอ่านหยุดหลังการเขียนเป็นชุด หน้าต่างคือผู้ต้องสงสัย หากการเขียนล้มเหลวทันที ความยาวคือผู้ต้องสงสัย

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

บทความที่เกี่ยวข้อง