הערמה של לקוח OpenVPN שני על מארח Linux אחד נכשלת באחד משני מקומות. או שהלקוח הפנימי אינו מצליח להגיע לשרת שלו משום שהמנהרה החיצונית השתלטה על התעבורה הזו, או שהלקוח השני משכתב את טבלת הניתוב והמנהרה הראשונה מפסיקה לעבוד. שתי הבעיות נמצאות בניתוב, לא בהצפנה, ושתיהן נפתרות לפני שהמנהרה החדשה נושאת תעבורת יישומים. הכתובות כאן להמחשה: tun0 היא המנהרה החיצונית, 203.0.113.9 הוא השרת הפנימי, ו-TCP 9999 היא הזרימה שחייבת להשתמש בנתיב הפנימי.

שני מצבי כשל, טבלת ניתוב אחת

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

הכשל השני מגיע מה-push של השרת. ה-push הרגיל הוא redirect-gateway def1, שמתקין 0.0.0.0/1 ו-128.0.0.0/1 במקום להחליף את נתיב ברירת המחדל. שני לקוחות שכל אחד מתקין זוג /1 משלו מתנגשים לכן, והזוג החדש ביותר מנצח כמעט בכל יעד. חפש את שני הקידומות האלה בטבלה כשמנהרה מתה אחרי שהשני מתחבר, כי ייתכן שנתיב ברירת המחדל עצמו מעולם לא השתנה.

סמן את הזרימה ותן לה טבלה

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

iptables -t mangle -A PREROUTING -p tcp -m multiport --dport 9999 -j MARK --set-mark 0x9999
ip rule add fwmark 0x9999 table 200 pref 1000
ip route add default dev tun1 table 200

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

קח את בעלות הניתוב מהלקוח השני

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

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

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

הצמד את שתי כתובות השרת

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

ip route add 203.0.113.9/32 dev tun0
ip route get 203.0.113.9

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

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

בדוק את השרשרת והפוך את הכשל לגלוי

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

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

סיים עם שתי זרימות ו-dump אחד של כללים: חיבור מסומן ואחד לא מסומן אמורים לעבור בנתיבים שונים, ו-ip rule show אמור להציג את העדיפות שהתכוונת אליה. אם שתי הזרימות עוברות באותו נתיב, הסימן לא מוחל במקום שבו אתה חושב.

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