Android VpnService: การอ่าน TUN ที่ค้าง เทียบกับข้อผิดพลาดการเขียน ENOBUFS
การอ่านที่ไม่กลับมาเลยกับการเขียนที่ล้มเหลวด้วย ENOBUFS มีสาเหตุตรงกันข้าม วัดหน้าต่างที่ประกาศและความยาวแพ็กเก็ตเพื่อเลือกวิธีแก้
อาการสองอย่างปรากฏในลูป 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 see | Where to look |
|---|---|
| Reader parked, peer send queue empty, last advertised window zero | Window updates in your ACK path |
| Write fails on one length and succeeds on a smaller one | MSS clamp and tunnel MTU |
| Both directions look fine, app still sees no data | Protected 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 ที่ทำงานได้เพียงจนถึงการเปลี่ยนแปลงเครือข่ายครั้งแรกเป็นปัญหาเรื่องการกำหนดเส้นทาง ไม่ใช่การควบคุมการไหล