Android VPN: ดิสนคริปเตอร์ของ VpnService กับอุปกรณ์ tun จาก root
แอป Android สร้าง tun0 เองไม่ได้: establish() คืนอะไร Binder หยุดที่ไหนและ JNI เริ่มที่ไหน และเมื่อไรอุปกรณ์ที่ root สร้างจึงชนะ
สองเส้นทางสามารถวางอินเทอร์เฟซ IP ไว้ข้างหน้าแอป Android และทั้งคู่จบลงที่ดิสนคริปเตอร์ไฟล์ชนิดเดียวกันซึ่งมีเจ้าของต่างกันมาก เส้นทางหนึ่งผ่าน VpnService และ builder ของมัน อีกเส้นทางผ่าน root shell และ /dev/net/tun การอ่านทุกครั้งบนดิสนคริปเตอร์นั้นคือแพ็กเก็ตขาออก และการเขียนทุกครั้งคือแพ็กเก็ตขาเข้า สิ่งที่แยกเส้นทางทั้งสองคือใครถือครองนโยบาย ใครอยู่รอดจากการอัปเกรด OS และคุณแตะตารางเส้นทางได้มากแค่ไหน
เลือกเส้นทางของแพลตฟอร์ม เว้นแต่คุณจะระบุสิ่งที่มันปฏิเสธจะทำได้อย่างเจาะจง การปฏิเสธนั้นมักเป็น TAP การดักจับนอกชุดแอปของคุณเอง หรือกฎเส้นทางที่เฟรมเวิร์กไม่เคยเปิดเผย
บนเส้นทางของแพลตฟอร์ม คุณประกาศเจตนาแทนคำสั่ง คุณระบุอย่างน้อยหนึ่งแอดเดรส เส้นทางที่ต้องการ เซิร์ฟเวอร์ DNS และรายการแอปแบบเลือกได้ จากนั้นบริการระบบของ Android จะทำการตั้งค่าที่ต้องใช้สิทธิ์พิเศษภายใต้ตัวตนของมันเอง คุณไม่เคยรู้ชื่ออินเทอร์เฟซล่วงหน้า และไม่เคยออก ioctl ที่สร้างมันขึ้นมา และนั่นคือประเด็น
สองวิธีในการได้อินเทอร์เฟซ IP
เส้นทาง root คือสูตรที่เก่ากว่า คุณเปิด /dev/net/tun เรียก TUNSETIFF ด้วย IFF_TUN ยกอินเทอร์เฟซขึ้นด้วย ip link และเพิ่มแอดเดรสกับเส้นทางด้วยมือ ทุกขั้นตอนเป็นของคุณที่จะทำให้ถูก รวมถึงขั้นตอนที่เฟรมเวิร์กจะจัดการให้อย่างเงียบ ๆ เส้นทางที่ขาดไปหนึ่งเส้นให้รายงานแก่ผู้ใช้เหมือนเซิร์ฟเวอร์ที่ตายแล้ว
การดีบักต่างกันพอ ๆ กับการตั้งค่า บนเส้นทาง root อินเทอร์เฟซมีอยู่ หรือไม่ก็ ioctl ของคุณคืนข้อผิดพลาดที่คุณอ่านได้โดยตรง บนเส้นทางของแพลตฟอร์ม อินเทอร์เฟซมีอยู่หลัง establish() สำเร็จเท่านั้น ความล้มเหลวจึงมาเป็นข้อยกเว้นหรือดิสนคริปเตอร์ null ไม่ใช่โหนดอุปกรณ์ที่คุณจิ้มดูได้
establish() คืนอะไรจริง ๆ
Builder.establish() มีสามผลลัพธ์ และแต่ละแบบต้องการการตอบสนองต่างกัน ดิสนคริปเตอร์หมายถึงอินเทอร์เฟซถูกตั้งค่าและเป็นของคุณที่จะใช้ null หมายถึงคำขอไม่ผ่าน และข้อยกเว้นที่ถูกโยนหมายถึงบริการระบบปฏิเสธ การรวม null กับความสำเร็จไว้ใน boolean เดียวเป็นวิธีที่พบบ่อยที่สุดที่แอปรายงานอุโมงค์ที่ทำงานอยู่ซึ่งไม่เคยบรรทุกแพ็กเก็ตเลย เอกสารอ้างอิงของ Builder ระบุข้อกำหนดเรื่องแอดเดรส และคุ้มค่าที่จะอ่านก่อนโทษเซิร์ฟเวอร์
ทิศทางถูกกำหนดโดยดิสนคริปเตอร์ ไม่ใช่โปรโตคอลของคุณ การอ่านให้แพ็กเก็ตขาออกที่ถูกเลือกไว้สำหรับ VPN แล้ว และการเขียนฉีดแพ็กเก็ตขาเข้ากลับเข้าอินเทอร์เฟซ การเข้ารหัส การพิสูจน์ตัวตน และการใส่เฟรมให้แพ็กเก็ตเหล่านั้นเป็นงานของทรานสปอร์ตของคุณเอง เพราะแพลตฟอร์มหยุดที่ขอบเขตของแพ็กเก็ต
Binder คือหนึ่งช่วง และ JNI คืออีกแกนหนึ่ง
คำขอของ builder ข้ามเข้าไปในระบบผ่าน Binder และที่ไหนสักแห่งใต้ลงไป แพลตฟอร์มเรียกโค้ดเนทีฟเพื่อตั้งค่าอุปกรณ์ นั่นคือสองกลไกที่ต่างกันซ้อนอยู่ในคำเรียกเดียว Binder คือการสื่อสารระหว่างโปรเซสระหว่างแอปของคุณกับบริการระบบ ส่วน JNI คือสะพานจาก Java ไปยังโค้ดเนทีฟภายในโปรเซส การอ่านทรี AOSP ของ Android 9 ทำให้สแตกดูเป็นรูปธรรม และควรจำไว้ว่าชื่อคลาสภายในกับเส้นทางไฟล์ย้ายที่ระหว่างรีลีส
สิ่งที่ไม่ย้ายที่คือสัญญาที่คุณพึ่งพาได้ ได้แก่ builder ดิสนคริปเตอร์ที่มันคืนมา และวงจรชีวิตของบริการรอบ ๆ ไม่มีแอปใดควรเรียกทรานแซกชันบริการภายในหรือสร้างโหนดอุปกรณ์ใหม่เพื่อให้ได้อินเทอร์เฟซ ถ้าโค้ดของคุณขึ้นกับชื่อคลาสของ AOSP มันจะพังบนรีลีสที่คุณไม่ได้ทดสอบ
TUN บรรทุก IP และ TAP บรรทุก Ethernet
TUN คืออินเทอร์เฟซ IP ส่วน TAP คืออินเทอร์เฟซ Ethernet และความต่างนั้นตัดสินว่าการบริดจ์เป็นไปได้หรือไม่ อุปกรณ์ TUN ไม่มีแอดเดรส MAC และไม่มีเฮดเดอร์ Ethernet ให้ส่งต่อ ลูกค้า TUN จึงเข้าร่วมบริดจ์เลเยอร์ 2 ไม่ได้ ไม่ว่าเส้นทางจะเขียนอย่างไร
VpnService ให้ IP variant ซึ่งเป็นสิ่งที่ VPN ระดับแอปต้องการและรองรับได้โดยไม่ต้องมีสแตก Ethernet ดิบ ถ้าดีไซน์ของคุณต้องการการบริดจ์ เส้นทางของแพลตฟอร์มจะพาคุณไปไม่ถึง และเส้นทาง root จะทำได้ก็ต่อเมื่อเคอร์เนลกับสิทธิ์ของอุปกรณ์ร่วมมือกัน
ที่ซึ่งเส้นทาง root ยังชนะ
สามงานผลักผู้คนออกจากเส้นทางของแพลตฟอร์ม การบริดจ์เลเยอร์ 2 ต้องใช้ TAP การดักจับทราฟฟิกจากแอปนอกขอบเขต VPN ของคุณต้องใช้ การมองเห็นที่เฟรมเวิร์กไม่ได้ให้ การเราต์ตามนโยบายที่ builder ไม่เคยเปิดเผย เช่นตารางแยกสำหรับปลายทางเดียว ต้องใช้กฎที่คุณติดตั้งเอง
ค่าใช้จ่ายมาทีหลัง: bootloader ที่ปลดล็อกหรืออิมเมจที่รูทแล้ว นโยบาย SELinux ที่เคลื่อนไปใต้เท้าคุณ และอุปกรณ์ที่เลิกทำงานถูกต้องหลัง OTA สูตรที่รูทแล้วยังส่งผ่านการแจกจ่ายแอปปกติไม่ได้ ทีมส่วนใหญ่จึงเก็บมันไว้เป็นเส้นทางวินิจฉัยภายในแทนที่จะเป็นผลิตภัณฑ์
การเลือกระหว่างสองอย่าง
เริ่มจากอินเทอร์เฟซที่คุณต้องการ ไม่ใช่โค้ดที่คุณมี ถ้าคำตอบคืออุปกรณ์ TUN ที่มีเส้นทางจำกัดเฉพาะแอป เส้นทางของแพลตฟอร์มจะสั้นกว่าและรอดจากการอัปเกรด ถ้าคำตอบเกี่ยวข้องกับเฟรม Ethernet การดักจับแอปอื่น หรือกฎเส้นทางที่ builder ปฏิเสธ คุณอยู่ในแดน root และควรตั้งงบเป็นต้นทุนการดำเนินงาน
การตรวจสอบที่ตัดสินข้อโต้แย้งส่วนใหญ่ได้: สร้างเส้นทางของแพลตฟอร์มก่อน บันทึกผลของ establish() แยกจากสถานะเซสชันระยะไกล และยืนยันหนึ่งแพ็กเก็ตในแต่ละทิศทาง จากนั้นจึงตัดสินว่าความสามารถที่ขาดไปคุ้มค่าที่จะออกจากเส้นทางที่รองรับหรือไม่