יש שתי דרכים להשאיר את החבילות של המנהרה עצמה מחוץ למנהרה: להגן על socket התעבורה, או להוציא את כל האפליקציה מה-VPN. הן פותרות בעיות שונות, ובחירה בהיקף הרחב יותר מפני שקל יותר לחבר אותו היא בדיוק הדרך שבה אפליקציות מאבדות גישה לרשת המקומית ול-DNS מפוצל בלי לשים לב. התחילו ממה שחייב להישאר מחוץ למנהרה, ואז בחרו בהיקף הקטן ביותר שמשיג זאת.

מה protect() מסיר מהמנהרה

VpnService.protect() הוא קריאה לכל socket בנפרד. אתם מוסרים לו את ה-socket או המתאר שנושא את המנהרה, ו-Android מסמן את ה-socket כך שהתעבורה שלו תשתמש ברשת שמתחת במקום בממשק ה-VPN. כל השאר שהאפליקציה שולחת ממשיך לעקוב אחרי המסלול הרגיל של ה-VPN.

ההיקף הצר הזה הוא העיקר. socket מוגן לא משנה איך לקוח ה-HTTP שלכם מפענח שמות, איזה ממשק משתמשים בו קריאות האנליטיקה שלכם, או מה קורה לחיבור שני שנפתח מאוחר יותר.

שלושה תנאים שקובעים את ההיקף

התנאי הראשון הוא אם רק תעבורת המנהרה צריכה לצאת. אם שאר התעבורה של האפליקציה צריכה לעבור דרך ה-VPN, הגנת socket היא ההיקף הנכון. התנאי השני הוא אם אתם שולטים ביצירת ה-socket. ספרייה שפותחת socket משלה עמוק בפנים לא משאירה לכם מקום לקרוא ל-protect() אלא אם היא חושפת hook או מקבלת socket בנוי מראש. התנאי השלישי הוא אם האפליקציה צריכה להגיע ישירות לרשת המקומית או ל-captive portal, וזו דרישה שונה משמירה על המנהרה בחיים.

החרגת האפליקציה באמצעות addDisallowedApplication() היא הכלי הגס. היא מסירה מה-VPN כל חיבור שהאפליקציה עושה, כולל אלה שרציתם לבדוק, ובחלק מהמכשירים היא גם משנה איך מתנהג גילוי מקומי.

אפשרות שלישית היא לקשור את התעבורה לרשת נבחרת באמצעות bindProcessToNetwork() או ידית רשת ברמת socket. זה שימושי כשהמכשיר מחובר גם ל-Wi-Fi וגם לסלולר בו-זמנית ואתם רוצים שהמנהרה תעבור בערוץ העלאה מסוים.

קראו לו בזמן שהתעבורה נוצרת

קראו ל-protect() באותו בלוק שיוצר את התעבורה, לפני connect() או send(). אם הקריאה מחזירה false, סגרו את ה-socket והכשילו את ההגדרה; המשך מותיר לכם תעבורה שהחבילות שלה עשויות לחזור לממשק שלכם.

הצורה קטנה מספיק כדי לשמור אותה במקום אחד:

DatagramSocket transport = new DatagramSocket();
if (!protect(transport)) {
    transport.close();
    throw new IOException("transport not protected");
}
transport.connect(serverAddress);

שלושה פרטים נושכים כאן. ראשית,הבוליאני הוא האות היחיד שמקבלים, אז תעדו את הכישלון עם מספיק הקשר כדי להבדיל בין הסכמה שבוטלה לבין קריאה שנדחתה. שנית,protect() מכסה את ה-socket, לא את מספר המתאר: אם סוגרים את ה-socket ומאוחר יותר משתמשים באותו מספר שלם ל-socket אחר, ה-socket החדש אינו מוגן. שלישית, כשספרייה מקורית פותחת את התעבורה, הגנו על המתאר לפני שהקוד המקורי מתחבר, אחרת המסלול אולי כבר נבחר.המסמך של VpnService הוא המקום לאמת אילו עומסי יתר רמת ה-API שלכם באמת מספקת.

בדקו את המסלול החסום, לא רק את המסלול המאושר

אמתו כשהמנהרה פעילה, כי זה המצב היחיד שבו שני המסלולים יכולים להיות שונים. שלחו בקשה אחת דרך התעבורה המוגנת ואחת ממסלול קוד שמעולם לא קרא ל-protect(), ואז השוו את מה שרואה הקצה המרוחק: החיבור המוגן אמור להגיע עם הרשת שמתחת כמקור, בעוד הלא-מוגן מציג את הכתובת של המנהרה עצמה ומזיז את מוני הבתים שלה. במכשיר,ss או netstat מציגים את אותו פיצול לפני שמשהו יוצא מהרדיו.

בדקו ביטול בנפרד, כי שינוי הסכמה מפיל את הממשק בלי לגעת בקוד ה-socket שלכם. בטלו את הסכמת ה-VPN או עצרו את השירות, בנו מחדש את התעבורה, ואמתו ש-protect() עדיין מחזיר true ושהתעבורה מתחברת מחדש ברגע שההסכמה ניתנת שוב.

אם הבקשה המוגנת עדיין הולכת אחרי המנהרה, קריאת ההגנה מתרחשת כנראה אחרי שה-socket כבר נותב. הזיזו אותה מוקדם יותר ובדקו שוב.

שמרו את ההחלטה כתובה לצד הגדרת התעבורה: אילו sockets מוגנים, איזו תעבורת אפליקציה מוחרגת, ומה האפליקציה עושה כשהגנה נכשלת. השורה הזו היא מה שמונע מריפקטור מאוחר להרחיב את ההחרגה לכל האפליקציה כי בדיקה אחת נכשלה. היא גם נותנת לבא בתור דרך לבדוק את הכוונה בלי הנדסה הפוכה של המניפסט.

מאמרים קשורים