כיצד לנתב רק את התעבורה של מכולות Docker דרך OpenVPN
השאירו את המארח בקישור הרגיל שלו והכניסו תת-רשת אחת של מכולות למנהרה: סדר NAT, כללי מקור, iroute של OpenVPN ובדיקות MSS.
החבילה של המכולה לעולם לא מגיעה אל tun0, ויכולות נוספות על המכולה לא ישנו זאת. בין האורח למנהרה עומדים שני חיפושי מסלול: אחד בתוך מרחב השמות של הרשת של האורח, אחד במארח לאחר שהמסגרת נוחתת על הגשר, והשנייה מתייעצת עם טבלה אחרת עם כללי התאמה אחרים. פריסת הייחוס שלי היא מארח Linux שמריץ OpenVPN לקוח שמחזיק 10.8.0.10 על tun0, גשר Docker על 172.18.0.0/16, וכתובת פרטית שרק צד המנהרה יכול להגיע אליה. המטרה נשארת צרה: תת-הרשת הזו של האורח יוצאת דרך המנהרה בזמן שהמארח שומר על הקישור הרגיל שלו.
הרצת לקוח ה-VPN בתוך המכולה היא עיצוב אחר, לא קיצור דרך. אז המכולה מחזיקה במנהרה ובפרטי ההזדהות שלה, והעבודה עוברת ל-Dockerfile במקום להישאר ב-iptables.
העברה בוחרת מסלול לפי יעד, ואז NAT כותב מחדש את המקור
כאשר net.ipv4.ip_forward=1 מוגדר, חבילה מועברת מנותבת לפי יעד בלבד, בטבלה הראשית. המקור לא משחק תפקיד בהחלטה הזו. רק לאחר בחירת המסלול מריץ הגרעין את POSTROUTING, ושם יושבת הכלל של Docker עצמו: -s 172.18.0.0/16 ! -o br-appnet -j MASQUERADE. כל מה שעוזב את הגשר לממשק אחר מתורגם, והכתובת שאליה הוא הופך תלויה בממשק שאליו המסלול שלח אותו.
הכלל הזה מסביר את רוב התסמינים ששואלים אותי עליהם. אם בטבלה הראשית אין מסלול ליעד הפרטי, החבילה יוצאת מהקישור עם הכתובת הפיזית של המארח. ספק האינטרנט שלכם מפיל את היעד, או שהאתר המרוחק מפיל מקור RFC 1918 שלא ציפה לו, והמכולה מגיעה לפסק זמן בזמן שהמנהרה נשארת למעלה ובריאה. לכידה על tun0 לא מראה דבר, וזה הסימן. אם המסלול אכן מצביע אל tun0, MASQUERADE כותב את המקור מחדש אל 10.8.0.10 ואותה מכולה עובדת בלי שתיגעו בדבר.
בקשו את החיפוש שממנה את מקור האורח. בלי from ו- iif המארח עונה עבור השקע של עצמו ומדווח על נתיב שהחבילה המועברת לעולם לא תיקח:
ip route get 192.168.11.222 from 172.18.0.5 iif br-appnetאם התשובה הזו מזכירה את הקישור, המנהרה לא בתמונה ושום הגדרה בצד המנהרה לא תעזור.
העבירו את האורח לכתובת של המנהרה עצמה
השינוי הקטן שעובד הוא שכתוב מקור מפורש ביציאה. הכניסו אותו במקום להוסיף אותו: Docker כבר יושב בראש POSTROUTING, ולכן כלל שנוסף עם -A מוערך אחרי זה של Docker ולעולם לא נורה.
iptables -t nat -I POSTROUTING 1 -s 172.18.0.0/16 -o tun0 -j SNAT --to-source 10.8.0.10שני תנאים קובעים אם זה יחזיק. הכתובת חייבת להיות כזו שהלקוח באמת מחזיק, אז נעצו אותה עם רשומה לכל לקוח שמכילה ifconfig-push 10.8.0.10 255.255.255.0; כלל שממנה כתובת שמעולם לא הוקצתה מייצר נפילות שקטות, לא שגיאה. והצד המרוחק חייב לקבל את המקור הזה ולנתב תשובות חזרה, ולכן רשימת ההיתרים שם ממנה את כתובת המנהרה ולא את תת-הרשת של האורח. כשכתובת הלקוח דינמית, -j MASQUERADE עם -o tun0 עוקב אחרי מה שהממשק מחזיק, במחיר של מדיניות מרוחקת שאי אפשר לכתוב נגדה ערך קבוע.
סינון הוא החצי השני. Docker מגדיר את מדיניות FORWARD ל-drop ומנקה את השרשראות שבבעלותו בכל הפעלה מחדש של הדמון, אז שימו מדיניות מקומית ב- DOCKER-USER, ששורד:
iptables -I DOCKER-USER -i br-appnet -o tun0 -j ACCEPT
iptables -I DOCKER-USER -i tun0 -o br-appnet -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTכלל החזרה חשוב: תשובה שמגיעה אל tun0 עבור חיבור שנפתח דרך הגשר אינה מתקבלת רק כי הכיוון היוצא התקבל.
החזיקו את המארח ישיר עם כלל מקור
כשהגשר כולו אמור להשתמש במנהרה אבל שום דבר אחר במארח לא, דלגו על NAT ותנו למקור הזה טבלה משלו. כתובות האורח נשארות אז גלויות מקצה לקצה.
echo "200 vpnbox" >> /etc/iproute2/rt_tables
ip rule add from 172.18.0.0/16 lookup vpnbox pref 1000
ip route add default dev tun0 table vpnboxכללים נבדקים לפני הטבלה הראשית, ו- from מתאים למקור. החבילות החיצוניות של המנהרה עצמה נושאות את המקור הפיזי של המארח, ולכן הן לעולם לא מתאימות ל- from 172.18.0.0/16 והמנהרה לא יכולה לנתב את עצמה חזרה לתוך עצמה. ההדרה העצמית הזו היא מה שעושה את הפריסה ליציבה.
מסלול החזרה זקוק כעת לעזרה. הצד המרוחק רואה 172.18.0.x לא מתורגמת ואין לו סיבה לדעת היכן היא נמצאת. בשרת, קובץ לכל לקוח צריך iroute 172.18.0.0 255.255.0.0 כדי שהטבלה הפנימית של השרת תמסור את הקידומת הזו ללקוח הזה, ו- route 172.18.0.0 255.255.0.0 שנדחף כדי שלקוחות אחרים ידעו שהיא קיימת.
שני בדיקות לפני שתסמכו על זה. הריצו ip route show default: עם redirect-gateway def1 הטבלה מחזיקה 0.0.0.0/1 ו- 128.0.0.0/1 דרך tun0, והמארח בכלל לא ישיר, כי חצאי ברירת המחדל האלה מנצחים בלי להחליף את default. כללים שנוספו עם ip rule add גם נעלמים באתחול, אז נשאו אותם בסקריפט up.
גודל החבילה הוא הכשל השני שתפגשו
ממשק גשר ב-1500 בייט נותן למנהרה יותר ממה שהיא יכולה לשאת ברגע שמוסיפים את כותרות ה-UDP וה-IP החיצוניות. חילופים קטנים עוברים, אז DNS נפתר ולחיצת היד של TLS מסתיימת, ואז העלאה גדולה יותר נתקעת בלי שום שגיאה בשום מקום. השולח ממשיך לחזור על אותו גודל מקטע כי הודעת ה-ICMP על צורך בפיצול, שהייתה אומרת לו להקטין, לעולם לא מגיעה.
תקנו את זה בממשק, ב-MSS, או בשניהם. ההידוק לא צריך שיתוף פעולה מהאורח:
ip link set dev tun0 mtu 1400
iptables -t mangle -A FORWARD -o tun0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtuההידוק כותב מחדש רק TCP. UDP, כולל QUIC, צריך במקום זאת ממשק אורח נמוך יותר, עם --opt com.docker.network.driver.mtu=1400 ביצירת הרשת או במפתח ה-compose המתאים.
DNS והבדיקות ששווה לחזור עליהן
פתרון שמות הולך באותו נתיב. הפותר המובנה של Docker ב- 127.0.0.11 מעביר לשרתים המוגדרים של המארח, אז מכולה יורשת את מה שהמארח משתמש בו. כשהאזור הפרטי נענה רק על ידי פותר שנגיש דרך המנהרה, כוונו את האורח לכתובת הזו, ובדקו עם getent hosts בתוך המכולה ולא במארח.
- הריצו את הבקשה שנכשלת בתוך האורח; בדיקה בצד המארח משתמשת בכתובת מקור אחרת ומסתירה את הבעיה.
- קראו את המונים בכללי ה-NAT והסינון שהוספתם. כלל בלי התאמות הוא טעות ניתוב, לא טעות מדיניות.
- ודאו שכתובת המנהרה בכלל ה-SNAT שלכם היא כזו שהלקוח באמת מחזיק.
- הנמיכו את ה-MTU של האורח או הדקו את ה-MSS לפני שתאשימו את השרת בקפיאה.
עברו על הרשימה ועצרו בכשל הראשון. השכבה הזו היא זו שצריך לתקן; כל דבר שמתווסף בהמשך רק מסתיר את הגבול שנשבר.