مدونة عمار الخوالده

الـ Interfaces والـ Sockets: الشبكة من الـ Hardware إلى الـ Software


الـ Interfaces والـ Sockets: الشبكة من الـ Hardware إلى الـ Software

مقدمة

كتبت مقدمة عن الشبكات وطبقات الـ OSI Model في (مقدمة في شبكات الحاسوب - Computer Networking)، في هذا المقال سأبدأ بالشرح ابتداء من الـ Physical Layer، وصولا الى كتابة كود لارسال و استقبال البيانات عبر الشبكة.

الأوامر والأمثلة الموجودة في المقال تم تنفيذها على جهاز يعمل بنظام Ubuntu، يفترض أن تعمل بنفس الطريقة على أي جهاز يعمل بـ Linux، قد تجد بعض الاختلافات البسيطة في حال تنفيذ الأوامر على جهاز يعمل بـ MacOS.

NIC - Network Interface Card

ذكرت في المقال السابق أن الـ Physical Medium هو الوسيط الذي يتم ربط الاجهزة عن طريقه (أسلاك كهربائية، أسلاك نقل الألياف الضوئية، اشارات لاسلكية…)، لكن لا بد من وجود قطعة متصلة بجهاز الحاسوب للتعامل مع الـ Physical Medium، فهي القطعة المسؤولة عن إرسال واستقبال الإشارات اللاسلكية، او القطعة التي تحتوي على المنفذ الخاص بسلك الشبكة.

يحتوي الحاسوب على Network Interface Card (NIC)، وهي القطعة المسؤولة عن استقبال البيانات من الـ Physical Medium أو إرسال البيانات عبره، وقد يحتوي الحاسوب على أكثر من NIC في نفس الوقت.

صورة لـ Ethernet Interface Card
مصدر الصورة (رابط)

الصورة السابقة توضح مثالا على NIC يتصل بالشبكة عن طريق منفذ Ethernet، هذا ليس إلا شكلا واحدا من الـ NIC، قد تكون الـ NIC قطعة مثبتة على اللوحة الرئيسية (اللوحة الأم) للحاسوب أو الهاتف “الذكي” أو موصولة بمنفذ USB.

يتعامل الـ NIC مع الـ Physical Medium لذلك يعّد جزءا من الـ Physical Layer، لكن للـ NIC عنوان MAC Address، والتعامل مع الـ Mac Address كما ذكرنا في المقال السابق من ضمن مسؤوليات الـ Data-Link Layer.

تصنيف الـ NIC ضمن أكثر من طبقة من طبقات الـ OSI Model يوضح ما ذكرناه في المقال السابق عن امكانية اختلاف التصميم (Design) عن التنفيذ (Implementation) وعدم انطباقه على الواقع بنسبة 100%

نفذ الأمر التالي لمعرفة الـ NICs الموجودة في جهازك:

sudo lshw -class network -short

هذه النتيجة على الجهاز الذي استخدمه:

H/W path           Device     Class          Description
========================================================
/0/100/1c/0        enp1s0     network        RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller
/0/100/1c.5/0      wlp2s0     network        Dual Band Wireless-AC 3165 Plus Bluetooth

الـ NIC الموجود في السطر الاول مسؤول عن الـ Ethernet، بينما الثاني خاص بالشبكات اللاسلكية ويدعم Bluetooth و WiFi.

كيف يتعامل نظام التشغيل مع الـ NIC

لكل قطعة مرتبطة بجهاز الحاسوب Device Driver (برنامج تعريف) للتعامل معها، الـ Driver يتواصل مع نظام التشغيل عن طريق الـ APIs التي توفرها نواة نظام التشغيل (Kernel).

في Unix والأنظمة الشبيهة به، توفر الـ Kernel واجهة interface للتعامل مع الشبكات، في الـ Output الخاص بالأمر الذي نفذناه لمعرفة الـ NICs الموجودة في الجهاز، ستجد العمود Device الذي يمثل اسم الجهاز في نظام التشغيل:

H/W path           Device     Class          Description
========================================================
/0/100/1c/0        enp1s0     network        RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller
/0/100/1c.5/0      wlp2s0     network        Dual Band Wireless-AC 3165 Plus Bluetooth

enp1s0 و wlp2s0 هي أسماء تعطيها الـ Kernel للـ NICs وتسمى Network Interface، يمكننا من خلال هذه الأسماء التعامل مع الشبكة وتنفيذ بعض الأوامر، الـ Network Interface هي تمثيل للـ NIC على مستوى الـ Software.

الأمر التالي مثلا يستعرض الـ MAC Address إضافة إلى بعض المعلومات الأخرى عن الـ Interface المرتبة بـ NIC الـ Wi-Fi على جهازي:

ip link show wlp2s0

صورة توضح نتيجة الأمر ip link show وتظهر الـ MAC Address الخاص بالـ Network Interface

الـ Internet Protocol والـ Interface

ما شرحناه سابقا منذ بداية المقال متعلق بالتفاعل بين الـ Physical Layer والـ Data-Link Layer، وإدارة الـ Kernel لذلك عن طريق الـ Network Interface.

بمعرفتك الآن أن تفاعل الـ Software مع الشبكة يتم عن طريق الـ Network Interface، قد تتساءل عن بروتوكول IP وما بني فوقه مثل TCP و UDP؟ لن أفصل في الـ Protocols في هذا المقال، سأكتفي حاليا بالإشارة إلى علاقة الـ IP Protocol بالـ Network Interface.

تعد “العنونة” Addressing من الوظائف الرئيسية لبروتوكول IP حيث يدير البروتوكول العناوين (IP Addresses) والتي يتم استخدامها لاحقا لعملية التوجيه (Routing).

يتم إسناد عناوين الـ IP للـ Network Interfaces، تعدد العناوين لنفس الـ Interface ممكن، تُسند العناوين تلقائيا باستخدام DHCP التي يستخدمها الـ Router الذي تستخدمه غالبا أو تُسند يدويا.

الجهاز المستخدم للتجربة متصل حاليا عن طريق الـ WiFi، الأمر التالي يمكنني من معرفة الـ IP Address المسندة للـ Network Interface

ip addr show wlp2s0

صورة تظهر الـ IP Addresses المرتبطة بالـ Network Interface الخاصة بالـ WiFi NIC
يمكنك رؤية وجود عنوان IPv4 وعنوانَي IPv6.

يوجد Caddy Server مثبت على الجهاز بغرض التجربة، الصورة التالية هي من متصفح على جهاز آخر متصل بنفس الشبكة:

صورة تظهر نتيجة تشغيل Caddy Server على المتصفح بالـ IP Address المرتبط بالـ Network Interface

سأضيف IP Address آخر بشكل يدوي:

sudo ip addr add dev wlp2s0 192.168.1.50

نتيجة تنفيذ أمر لإضافة IP Address مخصص للـ Interface

تمت إضافة الـ IP Address الجديد دون خسارة الـ IP Address السابق، وهو يعمل دون مشاكل:

نتيجة تشغيل Caddy Server على المتصفح باستخدام الـ IP Address الذي تمت اضافته يدويا

سأحاول إضافة IP Address مختلف هذه المرة:

sudo ip addr add dev wlp2s0 192.168.3.3

تنفيذ أمر اضافة الـ IP Address بشكل يدوي، مع إضافة IP Address خارج الـ Subnet

تمت إضافته بنجاح، لكن الاتصال يفشل:

نتيجة محاولة الوصول إلى IP Address خارج الـ Subnet

سبب ذلك أنني متصل بالـ Router و الـ Router في هذه الحالة هو الـ Gateway أو المسؤول الفعلي عن إدارة الاتصال، بالنظر إلى إعدادات الـ Router الخاص بي، وجدت أن IP Address للـ Router هو 192.168.1.1 والـ Subnet Mask هو 255.255.255.0، الـ IP الذي تمت إضافته لا ينتمي إلى الـ Subnet، كان بالإمكان أن يعمل لو كان الـ Subnet Mask هو 255.255.0.0.

Virtual Network Interfaces

الـ Network Interfaces التي استعرضناها في القسم السابق تقدم تمثيلا على شكل Software للـ NICs الموجودة في الجهاز، قد تكون الـ Network Interface افتراضية أيضا (Virtual).

استخدمنا سابقا lshw الذي يعرض الـ Hardware المرتبط بالجهاز، لكن لو استخدمنا أمرا يعرض الـ Interfaces سترى أن بعضها غير مرتبط بجهاز، يمكن استعراضها عن طريق الأمر ip link:

ip link

عرض قائمة الـ Network Interfaces

الـ Virtual Interfaces مفيدة في بعض أنواع البرامج، فبرامج الـ VPN مثلا تضيف Interfaces يمر الاتصال عن طريقها، فيمكنك إرسال البيانات عبر اتصال VPN المشفر، او إرسالها مباشرة دون الحاجة إلى الـ VPN حتى لو لم تغلق برنامج الـ VPN.

برامج الـ Virtualization التي تساعدك على تشغيل أجهزة وهمية Virtual Machines تضيف Interfaces لعمل Interfaces خاصة بكل VM، او انشاء شبكات وهمية بين الـ VMs.

اذا كنت مهتما بأنواع الـ Network Interfaces أو برمجة انظمة تتعامل معها يمكنك مراجعة المقالات التالية:

Loopback Interface

توجد Interface باسم lo في الصورة السابقة، هذه الـ Interface تسمى Loopback Interface وهي Interface مميزة في نظام التشغيل.

الـ Interface المرتبطة بـ NIC مثل wlp2s0 تستقبل البيانات من الـ NIC أو ترسلها عبره، بينما ترسل الـ Loopback Interface أي بيانات تستقبلها ثانية إلى نفس الجهاز كما في الصورة:

صورة توضح الفرق بين Interface خاصة بـ NIC و Loopback Interface

الـ Loopback Interface مفيدة لإنشاء اتصال شبكة بين أكثر من Process (أو نفس الـ Process) على نفس الجهاز، وهذا ما يحصل عند انشاء اتصال واستخدام localhost أو 127.0.0.1 كـ Host، عند تنفيذ الأمر ip addr show lo يمكن أن نرى 127.0.0.1 كـ IP Address لهذه الـ Interface:

نتيجة تنفيذ أمر لمعرفة الـ IP Address المسند إلى الـ Loppback Interface

الـ Socket وتعامل المبرمج مع الشبكات

يوفر نظام التشغيل APIs حتى تتعامل البرامج في الـ Application Layer مع الشبكة، سأشرح بشكل عملي قدر الامكان في هذا القسم من المقال، سأستخدم لغة Go في الأمثلة، لكن المفاهيم نفسها بغض النظر عن اللغة المستخدمة.

الأمثلة الموجودة بلغة Go لن تكون مكتوبة باستخدام net package، بل سأكتبها باستخدام sys/unix package، في معظم الحالات يفضل استخدام net، لكنني ساستخدم sys/unix لغايات الشرح، لأن أسماء الـ Functions في sys/unix تطابق الـ Syscalls الموجودة في نظام التشغيل، مما يسهل شرح المفاهيم أكثر.

التعامل مع الـ File System او الشبكات أو إدارة الـ Processes والـ Threads هي مسؤولية نظام التشغيل، الـ Syscalls هي عبارة عن API يوفره نظام التشغيل للتعامل معها.

أي اتصال عبر الشبكة يتم عن طريق استخدام ()socket1، الـ Socket هي الطريقة التي يوفرها نظام التشغيل للتعامل مع الشبكة.

عند تنفيذ socket() Function يُنشئ نظام التشغيل File Descriptor، الـ File Descriptor عبارة عن رقم مرجعي (Reference) لملف في نظام التشغيل، هذا الملف يكون مسؤولا عن الـ Socket من ناحية ارسال واستقبال البيانات.

الملف الخاص بالـ Socket والذي يمثله الـ File Descriptor ليس ملفا فعليا مخزنا على الـ Disk بالضرورة، قد تكون مجموعة من القيم المخزنة في الـ Memory، والـ File Descriptor هو المرجع المستخدم للإشارة إلى هذه القيم.

هذا مثال بسيط على انشاء Socket والاتصال بسيرفر TCP وارسال HTTP Request:

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6
 7    "golang.org/x/sys/unix"
 8)
 9
10func main() {
11    // Create a TCP socket and get the file descriptor
12    fileDescriptor, err := unix.Socket(unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_TCP)
13    if err != nil {
14        log.Fatal("Could not create socket: ", err)
15    }
16
17    // Will be executed after return.
18    // It should cleanup the memory
19    defer unix.Close(fileDescriptor)
20
21    // Connect to the TCP server
22    err = unix.Connect(fileDescriptor, &unix.SockaddrInet4{
23        Addr: [4]byte{127, 0, 0, 1},
24        Port: 3000,
25    })
26    if err != nil {
27        log.Fatal("Connection error: ", err)
28    }
29
30    // Send data over socket
31    unix.Write(fileDescriptor, []byte("GET / HTTP/1.0\r\n\r\n"))
32
33    // Read until there is no more data
34    for {
35        msg := make([]byte, 1024)
36        n, _, err := unix.Recvfrom(fileDescriptor, msg, 0)
37
38        if err != nil {
39            log.Fatal(err)
40        }
41
42        if n == 0 {
43            fmt.Println("Done")
44            return
45        }
46
47        fmt.Println(string(msg))
48    }
49}

الكود السابق ينشئ TCP Socket، مما سيعطينا الـ file descriptor وهو عبارة عن Integer، لاحظ أن كل Function يتم استدعاؤها للاتصال او ارسال واستقبال البيانات نمرر لها الـ file descriptor.

الهدف من هذا الكود تعليمي فقط، توجد العديد من الحالات التي لم اتعامل معها، إضافة إلى أن التعامل مع الأخطاء بطباعتها واغلاق البرنامج فقط لا يكفي. لغة Go تتعامل مع الأخطاء كقيم (Values).

هذه نتيجة تنفيذ الكود السابق، مع العلم انني شغلت HTTP Server على نفس الجهاز على البورت 3000:

نتيجة تنفيذ البرنامج الذي يتعامل مع الـ Socket

الـ Interface المستخدمة هي Loopback بما أن الـ Server والـ Client على نفس الجهاز، لا نحدد الـ Interface مباشرة ضمن الكود، لكن الـ Kernel تحدد الـ Interface التي يجب استخدامها بناء على عنوان الـ IP الموجود في الكود إضافة إلى الـ Routing Tables & Routing Rules.

صورة توضح اختيار الـ Kernel للـ Interface

الـ Routing Tables هي عبارة عن مجموعة من القواعد التي تستخدمها الـ Kernel لتحديد الـ Interface التي يجب استخدامها، يتم ذلك بناء على عدة عوامل منها الـ IP Address، وبعض القواعد المعرفة ضمن الـ Routing Tables.

في مثالنا في الأعلى استخدمنا العنوان 127.0.0.1، وبما أن هذا العنوان مرتبط بالـ Loopback Interface يتم استخدامها.

عند عمل Server يتم عمل bind()2 على IP Address محدد، عند استخدام 127.0.0.1 فهذا يعني أن الـ Server الخاص بك لن يتم الوصول إليه أبدا من خارج الجهاز، أما اذا استخدمت الـ Private IP الموجود ضمن شبكتك، يمكن للاجهزة في نفس الـ Subnet فقط الوصول الى السيرفر.

قد تحتاج أيضا لاستخدام 0.0.0.0 كـ IP Address، وهو IP Address خاص، استخدامه يعني ابلاغ الـ Kernel بقراءة الـ Packets من جميع الـ Interfaces.

خاتمة

الهدف من هذا المقال توضيح بعض المفاهيم والمصطلحات فقط، فهمك للـ Network Interfaces والـ Sockets مهم جدا، لكن غالبا لن يتعامل أغلب المبرمجين معها بشكل مباشر، إذا كنت مهتما ببرمجة الشبكات والـ Interfaces والـ Drivers، يمكنك تعلم تفاصيل أكثر حول الـ Interfaces وأنواعها بدءا من المقالات التي ذكرتها في الأعلى.

استفدت من مقال From Sockets to NIC: A Big Picture كثيرا عند كتابتي لهذا المقال.

يمكنك أيضا الرجوع إلى كتاب Linux Device Drivers الذي يتحدث عن برمجة الـ Driver في أنظمة Linux، الفصل السابع عشر تحديدا يتحدث عن الـ Network Drivers.

اقرأ أيضا