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

อ็อบเจกต์ตัวจัดการเป็นมุมมองที่แคชไว้ ไม่ใช่อุโมงค์

NETunnelProviderManager ถูกเติมจากที่เก็บค่ากำหนดที่ระบบเป็นเจ้าของ คุณสมบัติจึงไม่มีความหมายจนกว่า loadFromPreferences จะเสร็จ หากอ่าน connection.status ก่อนหน้านั้นจะได้ค่าเริ่มต้น และนั่นคือเหตุผลที่การรีเฟรชสถานะครั้งแรกหลังรีสตาร์ทรายงานว่าตัดการเชื่อมต่อทั้งที่อุโมงค์ทำงานอยู่ โหลดก่อน แล้วจึงอ่านสถานะและการกำหนดค่าโปรโตคอลจากตัวจัดการเสร็จสิ้นตัวเดียวกัน ติดตามการเปลี่ยนแปลงภายหลังผ่าน NEVPNStatusDidChange แทนการสำรวจซ้ำ และสังเกตว่าการแจ้งเตือนยังทำงานกับการเชื่อมต่อที่ On Demand เริ่มด้วย ซึ่งไม่มีแม้แต่การเรียกของแอปให้คืนค่าใด ๆ ค่าเดียวกันใช้กับการตั้งค่าโปรโตคอล คืออ่านจากการกำหนดค่าที่โหลดไว้ ไม่ใช่จากสิ่งที่แอปส่งเข้าไป

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

ลำดับการเริ่มทำงาน: บันทึก โหลด แล้วจึงเริ่ม

การเชื่อมต่อแบบผู้ให้บริการต้องทำสามขั้นตามลำดับ เขียนการกำหนดค่าของผู้ให้บริการและเรียก saveToPreferences โหลดตัวจัดการให้การกำหนดค่าที่บันทึกไว้เป็นอันที่คุณถืออยู่ แล้วจึงเริ่มผ่าน NETunnelProviderSession.startVPNTunnel() หากข้ามการบันทึก การเริ่มก็ไม่มีอะไรให้เริ่ม หากโหลดก่อนบันทึก ตัวจัดการก็ยังสะท้อนการกำหนดค่าเก่า

ค่าบูลีนที่ startVPNTunnel คืนมาบอกเพียงว่าคำขอถูกยอมรับ ความล้มเหลวระดับผู้ให้บริการจะมาทีหลังเป็นการเปลี่ยนสถานะเป็นตัดการเชื่อมต่อ ซึ่งคุณสังเกตได้จากการแจ้งเตือน NEVPNStatusDidChange และตัวจัดการการแจ้งเตือนต้องอ่าน connection.status ใหม่หลัง loadFromPreferences เพราะค่าที่แคชไว้อาจยังเป็นค่าเก่า ความล้มเหลวระดับคำขอจะกลับมาทันทีจากรูปแบบที่โยนข้อยกเว้นของการเรียก หรือผ่านพารามิเตอร์เอาต์พุตข้อผิดพลาดของ startVPNTunnelAndReturnError: โค้ดที่ถือว่าค่าที่คืนคือผลลัพธ์จะรายงานการเชื่อมต่อที่ไม่เคยถูกสร้างขึ้น ความล้มเหลวเงียบอีกอย่างคือตัวระบุบันเดิลของผู้ให้บริการที่ไม่ตรงกับส่วนขยายที่ติดตั้ง คำขอจะถูกปฏิเสธ และไม่มีอะไรในข้อความชี้ไปที่ตัวระบุนั้น หากตัวระบุผิด คำขอมักล้มเหลวทันที ก่อนที่บรรทัดล็อกของผู้ให้บริการจะปรากฏ

การโหลดค่ากำหนดใหม่หลังเริ่มทำงานคือสิ่งที่ทำให้หน้าจอสถานะถูกต้อง ตัวจัดการไม่ได้ผลักการกำหนดค่าใหม่ให้คุณเมื่อระบบเปลี่ยนมัน

วัสดุความเชื่อถือมาทางโปรไฟล์ ไม่เคยมาจากโค้ดแอป

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

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

ตัวนับมาจากอินเทอร์เฟซ ไม่ใช่จาก API ของ VPN

ตัวจัดการเปิดเผยสถานะและการกำหนดค่า และไม่มีอะไรที่นับไบต์ต่ออุโมงค์ หากต้องการตัวเลขการใช้งาน ให้เรียก getifaddrs() และอ่าน if_data ของอินเทอร์เฟซที่เกี่ยวข้อง คือ en0 สำหรับ Wi-Fi อินเทอร์เฟซ pdp_ip สำหรับเซลลูลาร์ และอุปกรณ์ utun สำหรับอุโมงค์เอง ตัวนับของ utun อธิบายทราฟฟิกภายในและรวมภาระของโปรโตคอลไว้ด้วย

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

การล้างข้อมูลอยู่ใน stopVPNTunnel ไม่ใช่ตอนสิ้นสุดโปรเซส

applicationWillTerminate ให้คุณเพียงไม่กี่วินาทีก่อนที่ระบบอาจฆ่าโปรเซส ซึ่งไม่ใช่ที่ที่เชื่อถือได้ในการรื้อการเชื่อมต่อ ส่งคำสั่งหยุดไปที่ stopVPNTunnel() หรือจัดการใน callback การหยุดของผู้ให้บริการ และเก็บตัวระบุตัวจัดการหรือการกำหนดค่าที่คุณเริ่มไว้ เพื่อให้การหยุดพุ่งเป้าไปที่การเชื่อมต่อนั้น ไม่ใช่ทุกการกำหนดค่าบนอุปกรณ์

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

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