למה אפליקציית ה-VPN ל-iOS מציגה מנותק כשהמנהרה פעילה
האפליקציה שלך שולטת במנהרה אבל לא מחזיקה בה, והמצב של המנהל נשמר במטמון. טען קודם את ההעדפות, ואז קרא את המצב ואת מוני הממשק.
האפליקציה שלך אומרת מנותק, שורת התפריטים אומרת מחובר, והתעבורה בבירור זורמת. הפער הזה אינו תנאי מרוץ שאפשר לתקן בניסיון חוזר: האפליקציה והמנהרה הן תהליכים נפרדים, והאובייקט שהקוד שלך קורא הוא תצוגה שמורה במטמון של תצורה שהמערכת מאחסנת. משטח ה-Personal VPN של אפל הוא 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() או טפל בה בקריאת החזרה של העצירה אצל הספק, ושמור את מזהה המנהל או התצורה שאיתו התחלת כדי שהעצירה תכוון לאותו חיבור ולא לכל תצורה במכשיר.
אחסן את המזהה הזה לצד הפרופיל שהוא שייך לו, כי אובייקט מנהל לא מתעד איזו תצורה המשתמש בחר. וזכור שחיבור ש-On Demand מעלה אין מאחוריו שום קריאת אפליקציה, ולכן מסך מצב עדיין חייב לטעון העדפות לפני שהוא מצייר משהו.