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

למה טבלת הניתוב לא יכולה לראות את התוכנית שלך

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

MechanismWhat selectsCoversOn tunnel drop
Socket bindsource addressa client you controlconnections fail
UID rule (Linux)owning userevery process of one accountconnections fail
Firewall profile (Windows)adapter profileone programfails closed
Network namespacenamespace membershipeverything started insideconnections fail
SOCKS hopapp proxy settingone app or one browser profileconnections fail

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

יעד בודד הוא עדיין נתיב, לא כלל תוכנית. ב-macOS המתכון הרגיל הוא להסיר את הסימון מ«שלח את כל התעבורה דרך VPN» ולהוסיף נתיב מארח: sudo route add -host internal-tuner.example -interface ppp0, שמוסר שוב עם sudo route delete -host internal-tuner.example -interface ppp0. קראו קודם את שם הממשק מתוך ifconfig, כי ppp0 הוא רק דוגמה. תווים כלליים לא עובדים בנתיב מארח, ולכן שירות שמדבר עם כמה שמות זקוק לרשומה אחת לכל שם, והכלל מכסה כל תהליך במחשב, לא את זה שהיה לכם בראש.

קשרו את הסוקט לכתובת המנהרה

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

import socket
s = socket.socket()
s.bind(("10.8.0.2", 0))
s.connect(("build.internal.example", 443))

כלים ברמה גבוהה יותר מציגים את אותו רעיון: curl --interface 10.8.0.2, CURLOPT_INTERFACE ב-PHP והארגומנט source_address בלקוחות ה-HTTP של Python. הכתובת חייבת להיות של המנהרה, ושגיאת הקלדה כאן נכשלת כשגיאת חיבור רגילה, לא כתלונת ניתוב. קישור לשם התקן במקום לכתובת הוא הרשאה של Linux ואינו עובר ל-Windows או ל-macOS, ולכן סקריפטים ניידים מעבירים את הכתובת.

Linux: התאימו את הבעלים, לא את שם התוכנית

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

ip rule add uidrange 1001-1001 lookup 100
ip route add default dev tun0 table 100

זה עובד רק אם המנהרה אינה כבר נתיב ברירת המחדל. הפעילו את הלקוח עם --route-nopull, או סננו את ההפניה הנדחפת עם --pull-filter ignore "redirect-gateway" ב-OpenVPN 2.4 ואילך, ואז הוסיפו בעצמכם את רשומת הטבלה. אחרת שני ברירות המחדל קיימות וזו עם העדיפות הנמוכה מנצחת. הגרסה המכוונת של זה היא להפנות את נתיב ברירת המחדל לשער המנהרה, או לעשות זאת מסקריפט ניתוק: זה מרחיק תעבורה מהאינטרנט כשהמנהרה נופלת, אבל זה מתג השבתה, לא בורר, והוא נשאר עד שמישהו מסיר אותו.

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

שני מצבי כשל מסבירים את רוב הדליפות. פתרון שמות עדיין מתרחש בפותר של המארח, ולכן השאילתה יוצאת מחוץ למנהרה גם כשחיבור ה-TCP נכנס פנימה. ו-IPv6 זקוק ל-ip -6 rule משלו, כי כלל v4 אינו מכסה אותו.

Windows ו-macOS: הגבילו את חומת האש או מסרו פרוקסי

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

macOS הולך באותה לוגיקה עם כלים אחרים. מאזין SOCKS שנגיש דרך SSH הוא האפשרות הפחות פולשנית, כי ssh -D 1080 user@host יוצר פורט מקומי אחד ורק אפליקציות שהוגדרו להשתמש בו הולכות בדרך הזו. כלל pf יכול לחסום תוכנית לפי משתמש או קבוצה, אבל הוא עדיין לא יכול לבחור את המנהרה רק לאותה תוכנית. Tunnelblick לא יכול לסנן לפי תחום בכלל, כי נתיבים עובדים עם כתובות; משתמשי OpenConnect פונים אל vpn-slice כשהשרת דוחף רשימת קידומות רחבה מדי.

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

קפיצת SOCKS לכלים בלי אפשרות קישור

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

ssh -N -D 127.0.0.1:9999 [email protected]

כל מה שמוגדר להשתמש בפורט הזה יוצא במבצר בזמן ששאר שולחן העבודה שומר על החיבור הרגיל שלו. מופע מקומי של 3proxy הקשור לכתובת המנהרה עושה את אותו דבר לפורט אחד בלי SSH, ו-stunnel מכסה פורט אחד עטוף ב-TLS. המגבלות מעשיות: פרוקסי נושא רק פרוטוקולים שיודעים להשתמש בו, UDP זקוק לשיוך UDP של SOCKS5 שהגדרת «SOCKS5» של לקוחות רבים אינה מפעילה, ו-DNS חייב להיפתר בידי הפרוקסי (socks5h ב-curl), אחרת השם דולף לפותר המקומי.

השאירו את המאזין על הלולאה המקומית אלא אם מכונה אחרת זקוקה לו.

אמתו את הנתיב בלכידה, לא בהודעת מצב

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

tcpdump -i any -n port 443
ss -tnp | grep 443

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

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

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