לדלג לתוכן

מחסנית הרשת בקרנל מוסברת - מה קורה בין חבילה שמגיעה לתוכנית שמקבלת אותה

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

המבנה שנוסע דרך כל השכבות - sk_buff

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

המסע של חבילה נכנסת

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

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

סוקט הוא בעצם עוד מתאר קובץ

כשקוראים ל-socket() בקוד, הקרנל בפועל יוצר מבנה נתונים פנימי שמייצג את החיבור, ומחזיר לתוכנית מתאר קובץ רגיל, בדיוק כמו שראינו עם קבצים וצינורות. זו הסיבה שאפשר להשתמש ב-read ו-write על סוקט כמעט כמו על קובץ רגיל - שכבת VFS מתייחסת אליו כאל עוד סוג של קובץ, עם קבוצת פעולות משלו שמופנית מתחת למכסה למחסנית הרשת.

netfilter - נקודות עצירה בדרך

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

המסע ההפוך - חבילה יוצאת

כשתוכנית קוראת ל-send, המסע קורה בכיוון ההפוך - שכבת התעבורה עוטפת את הנתונים בכותרת TCP או UDP, שכבת הרשת מוסיפה כותרת IP ומחליטה על הניתוב, ולבסוף שכבת קישור הנתונים עוטפת הכל בכותרת אתרנט ומעבירה לדרייבר של כרטיס הרשת, שמעביר את הביטים בפועל החוצה. כל שכבה מוסיפה את הכותרת שלה, בדיוק בסדר ההפוך מזה שבו הן הוסרו כשחבילה נכנסת.

למה כדאי להבין את זה

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

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

ואם מנגנון netfilter או sk_buff נשמעים לכם מורכבים מדי בהתחלה, יש קהילה שכבר עברה את זה.

הצטרפו לקהילה בדיסקורד

לסיכום

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