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

מה המערכת נותנת ומה לא

establish() מחזיר ParcelFileDescriptor עבור ממשק tun. המערכת כותבת לתוכו חבילות IP יוצאות ומצפה שהתהליך שלכם יקרא אותן, יחליט לאן הן הולכות וישלח אותן דרך סוקט רגיל. מתחת אין העברה בגרעין, ולכן כשאיש אינו קורא החבילות נזרקות ומוני הממשק לעולם לא זזים.

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

קריאה חוסמת קובעת אם הלולאה זזה

setBlocking(true) מכניס את המתאר למצב חוסם וקיים מאז API 21. בגרסאות ישנות הפתרון הוא עוזר JNI קטן שמנקה את O_NONBLOCK באמצעות fcntl על המתאר הגולמי. המצב חשוב כי הוא קובע מה מותר לקריאה בלי חבילה ממתינה לעשות.

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

קראו חבילה אחת בכל קריאה אל תוך מאגר בגודל ה-MTU של הממשק, והתייחסו לאפס או לערך שלילי כשגיאה ולא כחבילה ריקה.

protect() לפני השליחה הראשונה, לא אחריה

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

הסדר הוא protect, אחר כך connect או bind, אחר כך send. קריאה ל-protect אחרי connect עלולה להשאיר את החלטת המסלול הקודמת במקומה, ולכן הסוקט ממשיך להשתמש בנתיב השגוי למרות שהקריאה החזירה true. בדקו את הערך הבוליאני שהיא מחזירה והיכשלו בקול כשהערך false במקום להיכנס לשליחה שאינה יכולה לעבוד.

שגיאת הרשאה מ-sendto על סוקט שמעולם לא נגע ב-protect() היא החתימה הרגילה של הטעות הזו.

דוגמת UDP אינה נושאת HTTPS

דוגמת ההעברה שרוב המדריכים מעתיקים מטפלת ב-UDP ושום דבר אחר. TCP צריך לחיצת יד, מספרי רצף, אישורים ושליחה חוזרת, ולולאה שמעתיקה מטענים בין מתאר tun לסוקט לא מספקת אף אחד מאלה. לכן שאילתת DNS ב-UDP יכולה לעבור בעוד כל בקשת HTTPS נתקעת. או הריצו מחסנית TCP במרחב המשתמש או מסרו את החיבורים לפרוקסי מקומי שמסיים TCP במכשיר ומעביר את הזרם.

בדקו את הפיצול לפני שתכתבו משהו מחדש: שלחו שאילתת UDP אחת ופתחו חיבור TCP אחד לאותו מארח. אם נתיב UDP עובד ונתיב TCP נתקע ב-SYN הראשון, הבעיה בקוד ההעברה ולא בשרת. ההשוואה האחת הזו חוסכת זמן רב שמבוזבז על קריאת יומני שרת שאין בהם דבר שימושי.

רשימת המסלולים ומסננים לפי אפליקציה מגבילים את מה שאתם רואים

addAddress, addRoute ו-addDns מגדירים מה המערכת שולחת למנהרה. יעד מחוץ לרשימת המסלולים לעולם לא מגיע למתאר שלכם, ולכן שום כמות של לוגיקת העברה לא תתקן אותו. תעדו את קריאות ה-builder שאתם מבצעים והשוו אותן לכתובות שהאפליקציה באמת מנסה להגיע אליהן.

לבחירה לפי אפליקציה יש רצפה משלה: addAllowedApplication ו-addDisallowedApplication דורשים API 21, ובגרסאות ישנות השיטות פשוט אינן קיימות. עיצוב שמסנן לפי חבילה אינו יכול לרוץ שם כלל.

הקמה מחדש משאירה מתארים מיושנים

קריאה נוספת ל-establish() מחזירה מתאר חדש וסוגרת את הממשק הקודם. המתאר הישן אינו תקף כעת, ולכן תהליכון שעדיין חסום עליו מקבל שגיאה, וייתכן שסוקטים שנוצרו לנתיב הישן צריכים להיות מוחלפים. onRevoke הוא הקריאה החוזרת למנהרה שהמשתמש או המערכת לקחו. החזקת מחזור החיים אצל בעלים אחד, שעוצר את הקורא לפני הקמה מחדש, מונעת את רוב מצבי מחובר-אך-מת.

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

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

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