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

שתי כותרות, שתי משפחות כתובות

במצב מנהרת IPsec חבילת ה-IPv6 הפנימית הופכת למטען מאחורי כותרת ESP. הכותרת החיצונית שנושאת את חבילת ה-ESP הזו יכולה להיות IPv4 או IPv6, והיא עוקבת אחרי local_addrs ו-remote_addrs. WireGuard עושה אותו דבר בפחות טקס: החבילה הפנימית היא המטען של דטה-גרם UDP אל קצה העמית. שניהם משאירים את הכותרת הפנימית ללא שינוי, ולכן הצד המרוחק יכול לנתב אותה לאחר הסרת המעטפת.

המסלול בין האתרים רואה רק UDP 4500 כשה-IPsec משתמש במעבר NAT, או UDP 51820 למאזין WireGuard ברירת מחדל. כל נתב בדרך יכול להיות IPv4 בלבד ותעבורת ה-IPv6 עדיין חוצה, מפני שהיא אף פעם לא מופיעה כחבילת IPv6 בדרך הזו. זה נותן פיצול ראשון נקי: tcpdump -ni eth0 udp port 51820 מראה את הדרך המוצפנת, tcpdump -ni wg0 ip6 מראה את התעבורה הפנימית. לכידה חיצונית עמוסה עם פנימית שקטה אומרת שהקרנל לא מכניס IPv6 למנהרה; זה ניתוב או מדיניות, לא מצב קישור.

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

הסלקטורים ו-AllowedIPs הם רשימת ההרשאות

IKEv2 מנהל משא ומתן על אילו קידומות פנימיות child SA רשאי לשאת. ב-strongSwan 5.9 ואילך כתיבתם ב-swanctl.conf נראית כך:

children {
  net6 {
    local_ts  = 2001:db8:aa::/48
    remote_ts = 2001:db8:bb::/48
    mode = tunnel
    esp_proposals = aes256gcm16-prfsha384-ecp384
    start_action = trap
  }
}

אם משמיטים את local_ts ו-remote_ts, strongSwan גוזר אותן מתת-הרשתות של הממשק שהוא בוחר. במארח דו-ערוצי הקיצור הזה לרוב נותן זוג IPv4 בלבד: ה-SA עולה ו-IPv6 נדחה לפני שהוא מגיע לקוד ה-ESP. כתיבת שתי הקידומות הופכת את ה-SA לראוי לשאת אותן, ו-start_action = trap מעלה אותו בחבילה התואמת הראשונה.

ל-WireGuard יש רשימה אחת שמבצעת שני תפקידים. בבלוק העמית, AllowedIPs = 2001:db8:bb::/48, fd00:bb::/48 הוא גם המסלול שמותקן בשליחה וגם המסנן שמופעל בקליטה: חבילה מפוענחת שמקורה הפנימי נופל מחוץ לרשימה של אותו עמית נזרקת בשקט. wg-quick מוסיף מסלול לכל רשומה, ולכן בלוק העמית שלמטה אינו צריך פקודת ip -6 route נפרדת.

[Interface]
Address = 2001:db8:aa:ff::1/64, fd00:aa:ff::1/64
ListenPort = 51820

[Peer]
PublicKey = <site-b-public-key>
Endpoint = 203.0.113.20:51820
AllowedIPs = 2001:db8:bb::/48, fd00:bb::/48

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

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

לינוקס לא מעביר IPv6 עד שתגידו, וההגדרה היא לפי מרחב שמות ולא לפי ממשק:

sysctl -w net.ipv6.conf.all.forwarding=1
ip -6 route add 2001:db8:bb::/48 dev wg0
ip -6 route get 2001:db8:bb::10 from 2001:db8:aa::10

השורה השנייה היא זו שאנשים שוכחים בתכנונים מבוססי מסלול. IPsec מבוסס מדיניות מתקין מדיניות XFRM שמתאימה לקידומות, אבל תצורה מבוססת מסלול עם vti6 או WireGuard צריכה מסלול IPv6 אמיתי שמצביע על אותו התקן. שמרו את ה-sysctl תחת /etc/sysctl.d/; אחרת אתחול מחזיר את ההעברה לכבוי בשקט.

להעברה יש תופעת לוואי שכדאי להכיר: הקרנל אז מתעלם מהכרזות נתב אלא אם net.ipv6.conf.<wan>.accept_ra הוא 2, ולכן שער שכתובת ה-WAN שלו מגיעה דרך SLAAC עלול לאבד את מסלול ברירת המחדל ברגע שתפעילו את ההעברה. הגדירו accept_ra = 2 על אותו ממשק באותו קובץ והמסלול שורד. גם הליבה של אתר B צריכה ללמוד את 2001:db8:aa::/48 משער המנהרה, במסלול סטטי או בפרוטוקול ניתוב מעל המנהרה, אחרת התשובה מתה בתוך הרשת של B עצמה.

בדיקה בארבעה שלבים שמאתרת את השבר

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

  1. סלקטורים: swanctl --list-sas אמור להראות את זוג קידומות ה-IPv6 על ה-child SA, או wg show wg0 allowed-ips את הקידומת המרוחקת עם latest-handshakes טרי.
  2. מסלול הקרנל: ip -6 route get 2001:db8:bb::10 חייב להשיב עם התקן המנהרה, לא עם ממשק ה-WAN.
  3. לכידה פנימית: tcpdump -ni wg0 ip6 בזמן פינג בין מארחי LAN אמיתיים. חבילות שיוצאות ואף אחת לא חוזרת מצביעות על המסלול או חומת האש של B; היעדר מוחלט של חבילות מצביע חזרה על שלב 1 או 2.
  4. יעד: במארח B, ss -6 -ltn מאשר שהשירות מאזין ב-IPv6, ומוני חומת האש המקומיים מראים אם החבילה התקבלה או הושלכה.

ה-MTU נכנס באותה סבב. נתבי IPv6 לא מפצלים, ולכן חבילה גדולה מדי מושלכת ו-Packet Too Big חוזר לשולח. אם לחיצות יד ופינגים קטנים עובדים אבל העברת קובץ נתקעת, הקטינו את המטען עד שהוא עובר: ping -6 -s 1400 -c 3 2001:db8:bb::10. לממשקי WireGuard יש כברירת מחדל MTU של 1420 בתים, ו-IPsec מעל IPv4 מחסיר יותר, ולכן הגדרת ה-MTU הפנימי עדיפה על תקווה לגילוי MTU מסלול דרך חומת אש שמשליכה ICMPv6.

מצבי כשל שנראים כמו בעיות חומת אש

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

השני הוא סינון ICMPv6. Neighbor Discovery ו-Packet Too Big הם חלק מהפעולה הרגילה, לא מאבחון, ומדיניות שמשליכה את כל ה-ICMPv6 יוצרת מנהרה שמתחברת, מאמתת ואז נתקעת בכל דבר גדול מחבילה קטנה. התירו את הסוגים שהמסלול תלוי בהם והשאירו את המונים דלוקים כדי שההשלכה תיראה.

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

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