Android always-on VPN: ทำไมชื่อปลายทางจึงถูกแปลงก่อน
Android บล็อกทราฟฟิกก่อนที่อุโมงค์จะมีอยู่ แต่ไคลเอนต์ก็ยังต้องแปลงชื่อปลายทางของตัวเอง นี่คือลำดับและจุดที่ติดขัด
โทรศัพท์รีบูต always-on เปิดอยู่ «บล็อกการเชื่อมต่อเมื่อไม่มี VPN» เปิดอยู่ แต่อุโมงค์ไม่เคยขึ้น ไม่มีอะไรตั้งค่าผิด: การบล็อกทำหน้าที่ของมัน และความพยายามของไคลเอนต์เองที่จะไปถึงปลายทางของมันก็อยู่ภายใต้การบล็อกนั้นเช่นกัน ชื่อปลายทางต้องถูกแปลงก่อนที่อุโมงค์จะมีอยู่ โดยไคลเอนต์ที่ได้รับอนุญาตให้คุยกับเครือข่ายในขณะที่แอปอื่นทั้งหมดไม่ได้รับอนุญาต การเข้าใจว่าองค์ประกอบใดทำสิ่งนี้ และตามลำดับใด คือสิ่งที่เปลี่ยนปริศนานี้ให้เป็นคำถามเรื่องการตั้งค่า
จริง ๆ แล้วตัวสร้าง VPN ขออะไร
แอป VPN บน Android ไม่ได้ส่งที่อยู่เซิร์ฟเวอร์ให้แพลตฟอร์ม มันสร้างอินเทอร์เฟซภายใน: ที่อยู่และเส้นทางสำหรับอุโมงค์ ตัวแปลงชื่อที่แอปควรใช้ภายใน จากนั้น establish() จะคืนตัวอธิบายไฟล์ที่แอปอ่านและเขียนเป็นแพ็กเก็ต IP ดิบ ปลายทางระยะไกลไม่ปรากฏในตัวสร้างนั้นเลย
นั่นคือข้อเท็จจริงเชิงโครงสร้างหลัก แพลตฟอร์มสร้างอินเทอร์เฟซ tun และบังคับใช้นโยบายบนนั้น แอปใช้งานการขนส่งในพื้นที่ผู้ใช้ ซึ่งหมายความว่าแอปเองเป็นผู้แปลงชื่อปลายทางและเปิดการเชื่อมต่อที่เข้ารหัส ดังนั้นปลายทางในรูปแบบชื่อโฮสต์จึงเป็นไปได้ — ไม่ใช่เพราะแพลตฟอร์มรองรับชื่อ แต่เพราะแพลตฟอร์มไม่เคยเห็นปลายทางเลย
ฟิลด์หนึ่งในตัวสร้างรับที่อยู่: เซิร์ฟเวอร์ DNS ซึ่งต้องเป็นที่อยู่ตัวแปลงชื่อแบบตัวเลข โดยปกติคือที่อยู่ที่ผู้ให้บริการต้องการสอบถามผ่านอุโมงค์ ฟิลด์นั้นอธิบายว่าแอปส่งคำถามไปที่ใดหลังจากอุโมงค์ขึ้นแล้ว มันไม่ได้บอกอะไรเกี่ยวกับวิธีที่ไคลเอนต์ค้นหาปลายทาง และการถือว่าทั้งสองอย่างเป็นการตั้งค่าเดียวกันคือจุดที่ความสับสนเริ่มต้น
คำถาม DNS สองข้อ ไม่ใช่ข้อเดียว
คำถามแรกคือ: แอปส่งคำถามไปที่ใดเมื่ออุโมงค์ขึ้นแล้ว? คำตอบคือที่อยู่ตัวแปลงชื่อภายในอุโมงค์ เข้าถึงได้ผ่านอินเทอร์เฟซ tun และเป็นตัวเลขโดยโครงสร้าง
คำถามที่สองคือ: ไคลเอนต์ค้นหาปลายทางของตัวเองก่อนที่อุโมงค์นั้นจะมีอยู่ได้อย่างไร การค้นหานั้นเกิดขึ้นนอกอุโมงค์ บนเครือข่ายพื้นฐาน ในโปรเซสของไคลเอนต์ มันใช้ชื่อได้ และต้องใช้ตัวแปลงชื่อที่อุปกรณ์เข้าถึงได้โดยไม่ต้องมี VPN
| Stage | Who resolves | Path | Failure looks like |
|---|---|---|---|
| Endpoint lookup | The VPN app | Underlying network, protected socket | Never connects; no tunnel interface traffic |
| Peer authentication | Client and server | Tunnel transport | Connects then drops; certificate or key error |
| Private name lookup | Apps via the tunnel | Resolver inside the tun | Tunnel up, internal sites do not resolve |
สามขั้น สามอาการที่แตกต่างกัน ข้อความข้อผิดพลาดเดียวที่บอกว่า «เชื่อมต่อไม่ได้» รวมทั้งสามเข้าด้วยกัน ซึ่งเป็นเหตุผลที่ผู้คนเริ่มแก้ฟิลด์เซิร์ฟเวอร์ DNS เพื่อแก้ปัญหาที่เกิดขึ้นก่อนที่ฟิลด์นั้นจะถูกอ่านด้วยซ้ำ
ทำไม bootstrap จึงติดขัด
always-on ที่เปิดการบล็อกหมายความว่าทราฟฟิกที่ไม่ได้อยู่ใน VPN จะถูกทิ้ง รวมถึงทราฟฟิกของแอปที่เป็นเจ้าของ VPN ทางออกชัดเจน: แอปเรียก protect() บนซ็อกเก็ต ซึ่งผูกมันกับเครือข่ายพื้นฐานเพื่อให้ข้ามอุโมงค์และได้รับอนุญาตให้ออกไป ไคลเอนต์ VPN ทุกรายต้องทำเช่นนี้สำหรับการขนส่งของตัวเอง ไม่เช่นนั้นจะไปถึงเซิร์ฟเวอร์ไม่ได้เลย
ตอนนี้ลองพิจารณาการติดตั้งที่ตัวแปลงชื่อเดียวที่รู้จักชื่อปลายทางนั้นอยู่ภายในอุโมงค์เอง ไคลเอนต์ต้องมีอุโมงค์เพื่อแปลงชื่อ และต้องมีชื่อเพื่อสร้างอุโมงค์ นี่คือการพึ่งพาแบบวงกลมจริง และไม่มีการตั้งค่าไคลเอนต์ใดแก้ได้ นี่คือข้อผิดพลาดในการออกแบบ ไม่ใช่บั๊ก
เวลาเผยให้เห็นการพึ่งพาเดียวกันในรูปแบบที่เบากว่า ระหว่างบูตก่อนปลดล็อกครั้งแรก อุปกรณ์อาจยังไม่มีเครือข่ายที่ใช้งานได้ บน captive portal เครือข่ายมีอยู่แต่บล็อกทุกอย่างยกเว้นพอร์ทัล เมื่อเปลี่ยนจาก Wi-Fi ไปมือถือ เครือข่ายพื้นฐานของซ็อกเก็ตที่ป้องกันไว้เปลี่ยน และไคลเอนต์ต้องสังเกตและแปลงใหม่ แต่ละอย่างเหล่านี้เป็นเหตุการณ์ bootstrap และแต่ละอย่างควรทดสอบอย่างตั้งใจแทนที่จะค้นพบตอนตี 3
ทำไมการตรึงคำตอบจึงไม่ใช่ทางแก้
ทางลัดที่ชวนให้ทำคือแปลงชื่อหนึ่งครั้งแล้ววางที่อยู่ที่ได้ลงในฟิลด์ตัวเลข หรือฮาร์ดโค้ดในการบิลด์ที่กำหนดเอง มันดูเหมือนได้ผล และเปลี่ยนชื่อให้เป็นภาพนิ่ง เมื่อผู้ให้บริการย้ายปลายทาง — การย้ายถิ่น การเปลี่ยนแปลง anycast ที่อยู่ระดับภูมิภาคใหม่ — ไคลเอนต์ยังคงหมุนหาอันเก่าและไม่มีอะไรใน UI อธิบายว่าทำไม
ระเบียน DNS มีเวลาอยู่ได้ก็เพื่อให้คำตอบเปลี่ยนได้ การแคชถูกกำหนดตามอายุนั้น ไม่ใช่ตลอดไป การตรึงคำตอบทิ้งกลไกที่ทำให้ชื่อดีกว่าที่อยู่ ขณะที่ยังคงต้นทุนการดำเนินงานของการใช้ชื่อไว้
การตรึงมักไปพร้อมกับนิสัยเสียข้อที่สอง: การผ่อนการตรวจสอบตัวตนเพราะการเชื่อมต่อล้มเหลวต่างไปจากเดิม การตรวจสอบตัวตนของเซิร์ฟเวอร์ไม่ใช่ส่วนหนึ่งของปัญหา bootstrap และการทำให้มันอ่อนลงคือการแลกความไม่สะดวก DNS กับความเสี่ยงถูกดักฟัง
การออกแบบที่ทนต่อการรีบูต
การจัดวางที่ยั่งยืนไม่หรูหรา ใช้ไคลเอนต์ที่ใช้งานซ็อกเก็ตที่ป้องกันอย่างถูกต้องและแปลงปลายทางบนเครือข่ายพื้นฐาน และให้ชื่อปลายทางที่แปลงได้แบบสาธารณะ หรือผ่านตัวแปลงชื่อที่เข้าถึงได้ก่อนอุโมงค์ เปิดการบล็อกไว้ การปิดทั้งหมดเพื่อให้ DNS ทำงานจะลบคุณสมบัติที่การติดตั้งนี้มีไว้
ที่ที่ปลายทางต้องเป็นส่วนตัว ให้เส้นทาง bootstrap ที่ชัดเจนที่แพลตฟอร์มใช้ได้ — ตัวแปลงชื่อที่เข้าถึงได้โดยไม่ต้องมีอุโมงค์ หรือการเชื่อมต่อสำหรับบริหารที่ผู้ให้บริการดูแล — และทำให้ข้อยกเว้นนั้นแคบและเป็นของนโยบาย ไม่ใช่ของสวิตช์ผู้ใช้ เอกสาร Android VpnService คือข้อมูลอ้างอิงว่าแพลตฟอร์มรับประกันอะไรเกี่ยวกับการป้องกันและวงจรชีวิต และ ข้อมูลอ้างอิง VpnService.Builder ยืนยันว่าฟิลด์ DNS รับที่อยู่แบบตัวเลข
จากนั้นตรวจสอบสามขั้นตามลำดับบนการเชื่อมต่อใหม่หลังรีบูต: ชื่อปลายทางถูกแปลง อุโมงค์ถูกสร้างและคู่สัญญาได้รับการพิสูจน์ตัวตน และชื่อส่วนตัวถูกแปลงผ่านตัวแปลงชื่อของอุโมงค์ หากขั้นแรกล้มเหลว ขั้นต่อ ๆ ไปทำงานไม่ได้เลย และการแก้ไขเป็นของเส้นทาง bootstrap — ไม่ใช่ฟิลด์ตัวแปลงชื่อเด็ดขาด
- การแปลงปลายทางเกิดขึ้นในแอป นอกอุโมงค์ บนซ็อกเก็ตที่ป้องกันไว้
- ฟิลด์ DNS ในตัวสร้างมีไว้สำหรับแอปภายในอุโมงค์ ไม่ใช่ปลายทางของไคลเอนต์เอง
- ตัวแปลงชื่อที่เข้าถึงได้ทางอุโมงค์เท่านั้นคือการติดขัด ไม่ใช่การเริ่มที่ช้า
- ทดสอบปลายทาง การพิสูจน์ตัวตน และการค้นหาชื่อส่วนตัวแยกกันหลังรีบูต