Android VpnService: קריאה תקועה מ-TUN מול שגיאות כתיבה ENOBUFS
לקריאה שלעולם אינה חוזרת ולכתיבה שנכשלת עם ENOBUFS יש סיבות הפוכות. מדדו את החלון המוכרז ואת אורך החבילה כדי לבחור את התיקון.
שני תסמינים מופיעים באותה לולאת VpnService וזוכים לאותו אשם: קורא שלעולם אינו חוזר, וכתיבה שנכשלת עם ENOBUFS. אלה אינם וריאציות של באג אחד. קריאה תקועה היא מחלוקת על בקרת זרימה עם העמית; כתיבה שנכשלה היא חבילה שהממשק לא יקבל. אם תתייחס אליהם כאל בעיה אחת, תבלה יום בכיוונון חוצצים שמעולם לא היו מעורבים. הפיצול שלמטה הוא איך אני מחליט איזו מחצית של הלולאה למדוד קודם.
לולאה אחת, שתי נקודות כשל בלתי תלויות
השירות שלך קורא חבילת IP מתיאור הקובץ של TUN, בוחן אותה וכותב תגובה חזרה לאותו תיאור. כל מה שנשבר במסלול הזה מגיע לאחד משני הקריאות האלה, ולכן שניהם מדווחים כבעיה במנהרה. לקריאות יש חוזים שונים. קריאה ממתינה שהקרנל ימסור לך חבילה, וכתיבה מוסרת לקרנל חבילה שמותר לו לסרב לה.
קריאות הן המחצית הקלה למחשבה. קריאה שלא מחזירה דבר או ממתינה לתעבורה שלעולם לא מגיעה או ממתינה מפני שהעמית הפסיק לשלוח.
קריאה תקועה היא מחלוקת על בקרת זרימה
קריאה מהתקן TUN נחסמת עד שחבילה זמינה. זו התנהגות מתוכננת, ולכן תהליך שחונה בתוך read נראה רגיל בקישור לא פעיל. זה הופך לתקיעה כשמחסנית ה-TCP שלך ממשיכה לקבל נתונים מהאפליקציה המקומית אך לעולם לא מכריזה על חלון גדול מספיק כדי שהעמית המרוחק ישלח עוד. העמית ממלא את החלון שהכרזת, ממתין ל-ACK ולעדכון חלון, ועוצר. אז לקורא שלך אין מה להחזיר, והאפליקציה רואה חיבור פתוח ושקט.
הסיבות הנפוצות הן מחסנית שכותבת לסוקט בלי לעקוב אחר החלון המוכרז, ואחת ששוכחת לשלוח עדכון חלון אחרי ריקון החוצץ שלה. מבחוץ הן נראות זהות: בייטים יוצאים, דבר לא חוזר, לא נזרקת שגיאה. חסימת תהליך הקריאה על פירוק, פתרון DNS או קלט/פלט דיסק יוצרת אותה צורת תסמין מסיבה בלתי קשורה, לכן ודא שהתהליך באמת בתוך read לפני שאתה מאשים את בקרת הזרימה.
בדוק את תור השליחה של העמית ואת החלון שהוא ראה לאחרונה. אם העמית מחזיק נתונים מפני שהחלון שלך אפס, התיקון הוא במסלול ה-ACK שלך, לא בקריאת ה-TUN.
ENOBUFS הוא בעיית גודל, לא מחסור בחוצץ
כתיבה שמחזירה ENOBUFS בממשק TUN בדרך כלל דוחה את אורך החבילה. לממשק יש MTU, חבילה גדולה ממנו לא ניתנת להכנסה לתור, וניסיון חוזר של אותה חבילה נכשל באותה צורה בכל פעם. הדטרמיניזם הזה הוא הסימן. מחסור אמיתי בחוצץ מתפנה כשהלחץ יורד, ואילו דחייה לפי גודל לא אכפת לה כמה זמן תמתין.
ל-TCP יש מנגנון בדיוק לזה, וזה השלב שאנשים מדלגים עליו: ה-clamp של MSS. כשאתה מייצר מקטעים במרחב המשתמש אתה בוחר את גודל המטען, ובחירה מה-MTU של הממשק המקומי במקום מה-MTU של המנהרה פולטת מקטעים שהמנהרה לא יכולה לשאת. הגבל לקטן מבין השניים. ה-clamp מכסה TCP בלבד, לכן דטגרם UDP גדול מדי חייב להיות מפוצל או נשמט על ידי הקוד שלך.
רשום ביומן את האורך של כל חבילה שאתה כותב בזמן שאתה מאתר תקלות. האורך שנכשל הוא התשובה, והוא לא ישתנה בין ניסיונות.
שלוש מדידות שמפרידות בין המקרים
אין לך צורך בדיבאגר לאף אחד משני הכשלים. שלוש תצפיות מספיקות כדי לנתב את עצמך לפרק הנכון, והן לוקחות פחות זמן מבנייה מחדש.
| What you see | Where to look |
|---|---|
| Reader parked, peer send queue empty, last advertised window zero | Window updates in your ACK path |
| Write fails on one length and succeeds on a smaller one | MSS clamp and tunnel MTU |
| Both directions look fine, app still sees no data | Protected socket and routing loop |
אם השורה השלישית מתאימה, הלולאה עצמה תקינה והבעיה נמצאת במקום שהמנהרה לא יכולה לראות: סוקט תעבורה לא מוגן שמעביר את תעבורת ה-VPN שלך חזרה לתוך המנהרה, מה שמופיע בדרך כלל כחיבור שמצליח ואז לא מספק דבר.
תקן את האינסטלציה ששני המסלולים תלויים בה
אף תסמין לא שווה מרדף לפני שהממשק נבנה כראוי. קריאות ה-builder שחשובות הן הכתובת, המסלול, ה-MTU וההסכמה, בסדר הזה:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()קבע את ה-MTU לערך האמיתי של המנהרה במקום להשאיר אותו במה שהדוגמה השתמשה. כל מה שתכתוב אחר כך מוגבל במספר הזה, ואי-התאמה בין ה-MTU שאתה קובע ל-MSS שאתה מגביל אליו היא הדרך הנפוצה ביותר להחזיר את כשל הכתיבה אחרי תיקון. אחר כך הגן על הסוקט שנושא תעבורה לשרת ה-VPN עצמו. בלי protect החבילות האלה מנותבות חזרה למנהרה שלך והחיבור לעולם לא מסתיים, מה שנראה כמו קריאה תקועה אבל הוא לולאת ניתוב.
אי אפשר לעקוף את ההסכמה. שירות שמתחיל בלי ש-prepare מחזיר null לא מקבל ממשק, ו-establish מחזיר null במקום לזרוק, לכן בדוק את התיאור לפני שאתה מתחיל את הלולאה. מדריך VpnService הוא המקום לאשר את חוזה ה-builder במקום להעתיק דוגמה.
אמת כיוון אחד בכל פעם
שלח הד UDP דרך המנהרה וצפה באורכי החבילות לפני שאתה נוגע ב-TCP. אחר כך פתח לחיצת יד וודא שהמקטעים נשארים מתחת ל-MSS שחישבת. ספור חבילות ולא חיבורים, כדי שתקיעה תופיע כפער במונה ולא כסוקט תלוי. אם הקריאות נעצרות אחרי פרץ של כתיבות, החלון הוא החשוד; אם הכתיבות נכשלות מיד, זה האורך.
אחר כך הפעל מחדש את המכשיר וחזור פעם אחת, כי VpnService שעובד רק עד שינוי הרשת הראשון הוא בעיית ניתוב ולא בעיית בקרת זרימה.