Android VpnService เชื่อมต่อได้แต่ไม่มีแพ็กเก็ตไปถึงเครือข่าย
อุโมงค์ขึ้นแล้วแต่หน้ายังล้มเหลว: ตรวจสอบโหมดอ่าน การป้องกันซ็อกเก็ต การรองรับ TCP และรายการเส้นทางก่อนโทษเซิร์ฟเวอร์ VPN
อุโมงค์ขึ้น ไอคอนกุญแจปรากฏ แต่ทุกคำขอในเบราว์เซอร์ยังหมดเวลา ในแอป VpnService การรวมกันนี้มักหมายความว่าลูปอ่านยังมีชีวิตและครึ่งเอาต์พุตเสีย ไม่ใช่เซิร์ฟเวอร์ปฏิเสธแฮนด์เชก Android สร้างอินเทอร์เฟซ ย้ายทราฟฟิกของแอปเข้าไป และมอบตัวอธิบายไฟล์ให้โปรเซสของคุณแล้ว ทุกอย่างหลังตัวอธิบายนั้นคือโค้ดของคุณ และการเรียกที่หายไปเพียงครั้งเดียวก็พอจะกลืนทั้งสตรีม
ระบบให้อะไรและไม่ให้อะไร
establish() คืนค่า ParcelFileDescriptor สำหรับอินเทอร์เฟซ tun ระบบเขียนแพ็กเก็ต IP ขาออกลงไปและคาดว่าโปรเซสของคุณจะอ่าน ตัดสินใจว่าไปไหน และส่งผ่านซ็อกเก็ตธรรมดา ข้างใต้ไม่มีการส่งต่อในเคอร์เนล ดังนั้นเมื่อไม่มีใครอ่าน แพ็กเก็ตจะถูกทิ้งและตัวนับของอินเทอร์เฟซไม่ขยับเลย
เริ่มด้วยการนับแทนการเดา เพิ่มตัวนับทุกครั้งที่อ่านสำเร็จและบันทึกปลายทางกับโปรโตคอลของแพ็กเก็ตแรก ๆ ถ้าตัวนับยังเป็นศูนย์ขณะที่มีทราฟฟิกเกิดขึ้น ความผิดอยู่เหนือโค้ดส่งต่อของคุณ ซึ่งจำกัดการค้นหาไว้ที่การตั้งค่า builder และโหมดอ่าน ถ้าตัวนับขยับแต่เบราว์เซอร์ยังล้มเหลว ความผิดอยู่ที่เส้นทางส่งหรือเส้นทางกลับ
การอ่านแบบบล็อกตัดสินว่าลูปจะเดินหรือไม่
setBlocking(true) ใส่ตัวอธิบายในโหมดบล็อกและมีมาตั้งแต่ API 21 ในเวอร์ชันเก่า ทางแก้คือตัวช่วย JNI เล็ก ๆ ที่ล้าง O_NONBLOCK ด้วย fcntl บนตัวอธิบายดิบ โหมดสำคัญเพราะเป็นตัวกำหนดว่าการอ่านที่ไม่มีแพ็กเก็ตรออยู่ทำอะไรได้
ในโหมดไม่บล็อก การอ่านนั้นคืนค่าลบทันที และลูปที่เพิกเฉยผลลัพธ์จะหมุนคอร์โดยไม่ขยับทราฟฟิกเลย ในโหมดบล็อก การอ่านจะรอ ซึ่งเป็นสิ่งที่เธรดส่งต่อต้องการ แต่เธรดจะถูกขัดจังหวะไม่ได้อีกต่อไป วิธีที่รองรับในการทำให้การอ่านล้มเหลวคือปิดตัวอธิบาย เพื่อให้ลูปออกได้ การฆ่าโปรเซสหรือขัดจังหวะเธรดอาจทำให้การเริ่มครั้งถัดไปถือตัวอธิบายที่ไม่ตรงกับอินเทอร์เฟซอีกต่อไป
อ่านหนึ่งแพ็กเก็ตต่อการเรียกเข้าไปในบัฟเฟอร์ขนาด MTU ของอินเทอร์เฟซ และถือว่าศูนย์หรือค่าลบเป็นข้อผิดพลาด ไม่ใช่แพ็กเก็ตว่าง
protect() ก่อนการส่งครั้งแรก ไม่ใช่หลัง
ทุกซ็อกเก็ตที่บรรทุกแพ็กเก็ตห่อหุ้มต้องถูกป้องกันก่อน ซ็อกเก็ตที่ไม่ถูกป้องกันจะเป็นไปตามกฎเส้นทางเดียวกับที่คุณเพิ่งติดตั้ง แพ็กเก็ตจึงกลับเข้าไปในอุโมงค์และลูปอ่านของคุณเองหยิบขึ้นมาอีกครั้ง อุโมงค์ก็จะส่งต่อเอาต์พุตของตัวเองไม่สิ้นสุด และจากภายนอกดูเหมือนการเชื่อมต่อที่เปิดแล้วไม่เคยตอบ
ลำดับคือ protect จากนั้น connect หรือ bind จากนั้น send การเรียก protect หลัง connect อาจทิ้งการตัดสินใจเส้นทางเดิมไว้ ซ็อกเก็ตจึงใช้เส้นทางผิดต่อไปแม้การเรียกคืนค่า true ตรวจสอบบูลีนที่คืนค่าและล้มเหลวเสียงดังเมื่อเป็น false แทนที่จะเข้าสู่การส่งที่ไม่สามารถทำงานได้
ข้อผิดพลาดเรื่องสิทธิ์จาก sendto บนซ็อกเก็ตที่ไม่เคยแตะ protect() คือลายเซ็นปกติของความผิดพลาดนี้
ตัวอย่าง UDP ไม่บรรทุก HTTPS
ตัวอย่างการส่งต่อที่บทเรียนส่วนใหญ่คัดลอกมาจัดการเฉพาะ UDP TCP ต้องมีแฮนด์เชก หมายเลขลำดับ การตอบรับ และการส่งซ้ำ และลูปที่คัดลอกเพย์โหลดระหว่างตัวอธิบาย tun กับซ็อกเก็ตไม่ได้ให้สิ่งเหล่านั้นเลย นี่คือเหตุผลที่คำถาม DNS ผ่าน UDP ผ่านไปได้ในขณะที่ทุกคำขอ HTTPS ติดขัด จงรันสแตก TCP ในพื้นที่ผู้ใช้ หรือมอบการเชื่อมต่อให้พร็อกซีโลคอลที่สิ้นสุด TCP บนอุปกรณ์และส่งต่อสตรีม
ทดสอบการแยกก่อนเขียนอะไรใหม่: ส่งคำถาม UDP หนึ่งรายการและเปิดการเชื่อมต่อ TCP หนึ่งรายการไปยังโฮสต์เดียวกัน ถ้าเส้นทาง UDP ทำงานและเส้นทาง TCP ติดที่ SYN แรก ปัญหาอยู่ที่โค้ดส่งต่อไม่ใช่เซิร์ฟเวอร์ การเปรียบเทียบครั้งเดียวนั้นประหยัดเวลามหาศาลที่เสียไปกับการอ่านบันทึกเซิร์ฟเวอร์ซึ่งไม่มีอะไรมีประโยชน์
รายการเส้นทางและตัวกรองตามแอปจำกัดสิ่งที่คุณเห็น
addAddress, addRoute และ addDns กำหนดว่าระบบส่งอะไรเข้าไปในอุโมงค์ ปลายทางนอกเส้นทางไม่มีวันไปถึงตัวอธิบายของคุณ ดังนั้นตรรกะการส่งต่อเท่าไรก็แก้ไม่ได้ บันทึกการเรียก builder ที่คุณทำและเทียบกับที่อยู่ที่แอปพยายามเข้าถึงจริง
การเลือกตามแอปมีขอบล่างของตัวเอง: addAllowedApplication และ addDisallowedApplication ต้องใช้ API 21 และในเวอร์ชันเก่าไม่มีเมธอดเหล่านั้นเลย การออกแบบที่กรองตามแพ็กเก็ตไม่สามารถทำงานที่นั่นได้เลย
การตั้งค่าใหม่ทิ้งตัวอธิบายเก่าไว้
การเรียก establish() อีกครั้งคืนตัวอธิบายใหม่และปิดอินเทอร์เฟซก่อนหน้า ตัวอธิบายเก่าไม่ถูกต้องแล้ว เธรดที่ยังบล็อกอยู่จึงได้รับข้อผิดพลาด และซ็อกเก็ตที่สร้างสำหรับเส้นทางเก่าอาจต้องเปลี่ยน onRevoke คือ callback สำหรับอุโมงค์ที่ผู้ใช้หรือระบบเก็บคืน การเก็บวงจรชีวิตไว้กับเจ้าของเดียวที่หยุดตัวอ่านก่อนตั้งค่าใหม่ช่วยป้องกันสถานะเชื่อมต่อแต่ตายส่วนใหญ่
อ่านข้อผิดพลาดแต่ละอย่างตามขั้นตอนที่มันสังกัด รีเซ็ตการเชื่อมต่อจาก recvfrom หมายความว่าปลายทางไกลปิดขณะโค้ดยังอ่านอยู่ ข้อผิดพลาดที่อยู่ไม่ถูกต้องชี้ไปที่การแยกวิเคราะห์ส่วนหัวหรือบั๊กออฟเซต ไม่ใช่เครือข่าย
ลำดับงาน: เปิดโหมดบล็อก หนึ่งแพ็กเก็ตต่อการอ่าน แยกวิเคราะห์ส่วนหัวแล้ว ป้องกันซ็อกเก็ตแล้ว ส่งเพย์โหลดแล้ว ปิดอย่างสะอาดเมื่อส่งมอบ และรายการเส้นทางที่ครอบคลุมปลายทาง