آشنایی با سوکت های شبکه در سی و لینوکس - بخش دوم

در دنباله نوشته های برنامه نویسی و آشنایی با معماری شبکه در لینوکس، در دومین نوشته از دسته نوشته های آشنایی با سوکت های شبکه در سی و لینوکس، می خواهم کمی درباره پیاده سازی آدرس IP4 (یا IPv4) در هسته لینوکس بگویم.

همانند دیگر مولفه های یک سیستم عامل مانند مدیریت فرآیندها، سیستم فایل، دیوایس های سخت افزای و دیگر مولفه ها، مولفه شبکه و زیر مولفه های آن درون هسته لینوکس پیاده سازی شده اند. هسته لینوکس بر پایه سی و تا اندازه ای اسمبلی (برای نمونه در بوت شدن هسته) نوشته شده است.

ساختار ها یا Structure ها در هسته لینوکس

سی زبانی رویه ای (بر پایه تابع ها) است و زمانی که شما هسته لینوکس را بررسی می کنید، بسیار با تابع ها، اشاره گرها و به ویژه ساختارها یا همان Structure ها بر می خورید. در واقع این ساختارها، نوع های داده ای هستند که در برنامه نویسی در سطح یا فضای کاربر، به کار گرفته می شوند.

هر یک از این ساختارها درون یکی از فایل سرآیند هسته لینوکس پیاده سازی شده اند. برای نمونه، ساختاری که یک کاربر لاگین کرده را نشان می دهد، در فایل سرآیند pwd.h پیاده سازی شده است. توجه کنید که یک ساختار یا struct در سی، دارای عضوهایی است که از طریق نام آن ساختار در دسترس هستند. به عبارت دیگر، یک کاربر با یک struct و هر ویژگی آن کاربر نیز با یکی از عضوهای struct پیاده سازی شده است.

ساختار آدرس سوکت عمومی

برنامه هایی که سوکت را به کار می برند، نیاز دارند تا این توانایی را داشته باشند که نشانی اینترنت (IP) و شماره درگاه (Port) را برای هسته لینوکس مشخص کنند. برای نمونه کلاینت باید نشانی و شماره درگاه از پیش مشخص شده روی سرور را باید در خود مشخص کند تا هسته از مقصد بسته (Packet) آگاه باشد.

در کنار این، نیاز است تا آدرس به برنامه فرستاده شود. برای نمونه، در پیاده سازی یک همانند Caller Id در تلفن ها که شماره تماس گیرنده نشان داده می شود، سرور باید این توانایی را داشته باشد که نشانی هر کلاینتی که با او ارتباط برقرار می کند را بخواند.

struct sockaddr
struct sockaddr

شکل بالا ساختار struct sockaddr که ساختار آدرس سوکت عمومی (Generic Socket Address Structure) را نشان می دهد. این ساختار اطلاعات آدرس سوکت را برای بسیاری از نوع های سوکت نگهداری می کند.

عضو sa_family که از نوع unsigned short است، سوکت آدرس فامیلی (Address Family) را نشان می دهد که در ادامه درباره آن گفته شده است. برای ساخت سوکت برای IPv4 باید آدرس فامیلی AF_INET و برای IPv6 باید AF_INET6 را به کار ببریم. اندازه sa_family دو بایت است زیرا اندازه فضای اشغالی برای unsigned short برابر با دو بایت است.

توجه کنید متغیری که از نوع unsigned short باشد می تواند مقداری میان بازه صفر تا ۶۵۵۳۵ را دریافت کند.

عضو دیگر sa_data است که به گونه یک رشته (آرایه ای از کاراکترها) به اندازه ۱۴ بایت شناسانده شده است. این عضو اطلاعاتی همچون نشانی IP و شماره درگاه مقصد را نگه می دارد. شماره پورت ها، شماره ای در بازه صفر تا ۶۵۵۳۵ استو می توان با ۲ بایت یا ۱۶ بیت نمایش داد و نشانی IPv4 نیز ۳۲ بیتی یا ۴ بایتی هستند.

ساختار آدرس سوکت

ساختار آدرس سوکت (Socket Address Structure) یا SAS یکی از چندین struct درون هسته لینوکس هستند. توجه کنید دو سوکت برای IPv4 و IPv6 داریم و از این رو به ازای IPv4 و IPv6 ساختارهای جدای از هم برای سوکت ها پیاده سازی شده است. برای پیاده سازی یا نمایش یک سوکت IPv4 در هسته لینوکس، دو ساختار به نام های struct in_addr و struct sockaddr_in پیاده سازی شده است که در آنها in کوتاه شده Internet است، زیرا در کنار سوکت های شبکه که ارتباط میان دو فرآیند در دو سوی شبکه را فراهم می کنند، سوکت های دیگری نیز در لینوکس هستند.

struct sockaddr_in - struct in_addr IPv4
struct sockaddr_in - struct in_addr IPv4

ساختار struct sockaddr_in

این ساختار دارای سه عضو به نام های sin_family از نوع sa_family_t و sin_port از نوع in_port_t و sin_addr از نوع ساختار دومی، یعنی struct in_addr است. اگر نوشته بخش یکم را به یاد داشته باشید، گفته شد که یک سوکت در برگیرنده یک دوتایی (IP, Port) است که در struct sockaddr_in می بینید که دو عضو sin_port و sin_addr به ترتیب به این دو اشاره دارند.

https://virgool.io/linux-internals/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D8%B3%D9%88%DA%A9%D8%AA-%D9%87%D8%A7%DB%8C-%D8%B4%D8%A8%DA%A9%D9%87-%D8%AF%D8%B1-%D8%B3%DB%8C-%D9%88-%D9%84%DB%8C%D9%86%D9%88%DA%A9%D8%B3-%D8%A8%D8%AE%D8%B4-%DB%8C%DA%A9%D9%85-wbohvfgswvmm
واژه sin کوتاه شده socket internet است.

نوع sin_port به گونه in_port_t است که در واقع یک نوع تعریف شده در سی است که به نوع uint16_t یا Unsigned Integer 16 bit اشاره می کند. خود in_port_t در فایل سرآیند به نام netinet/in.h و uint16_t نیز در فایل سرآیند inttypes.h شناسانده شده است. در واقع uint16_t یک مقدار صحیح ۱۶ بیتی به علامت (مثبت) است که بیشترین مقدار آن برابر با ۶۵۵۳۵ است و چون in_port_t نیز به uint16_t اشاره دارد، پس می توان با آن یک شماره پورت از صفر تا بیشترین و بالاترین شماره، یعنی ۶۵۵۳۵ را نگهداری کرد.

توجه کنید تعداد یا بازه شماره پورت ها از شماره صفر تا ۶۵۵۳۵ است که این برپایه استاندارد IEEE است.

عضو دیگر، یعنی sin_len از نوع شماره صحیح بی علامت ۸ بیتی uint8_t است که به منظور پشتیبانی از BSD 4.3 به بعد به ساختار struct sockaddr_in افزوده شده است و هرگز استفاده و مقداردهی (تنظیم) نمی شود. اگر زمانی راهنمای man ip را در لینوکس خواندید، شاید این عضو در ساختار نوشته شده در راهنما نباشد و نخستین عضو sin_family باشد. آدرس فامیلی که در نوشته دیگری درباره آن خواهم گفت، برای IPv4 همیشه برابر با AF_INET خواهد بود.

برای برنامه نویسی سوکت در لینوکس، تنها سه عضو sin_family و sin_port و sin_addr نیاز هستند.

ساختار struct in_addr

عضو دیگر sin_addr است که در واقع یک آدرس IPv4 را نشان می دهد. آدرس های IPv4 به صورت ۳۲ بیت مانند 192.168.1.100 یا 127.0.0.1 یا 8.8.8.8 هستند. sin_addr خودش از نوع ساختار دیگر یعنی struct in_addr است. همانگونه که می بینید خود struct in_addr دارای یک عضو به نام s_addr (یا socket address) است که از نوع in_addr_t است و in_addr_t خودش اشاره به نوع uint32_t دارد که یک نوع صحیح بی علامت ۳۲ یبتی است.

شکل زیر ساختار struct sockaddr_in را نشان می دهد که در آن سه مولفه اصلی، یعنی sin_family و sin_port و sin_addr به ترتیب ۱ و ۲ و ۸ بایت یا ۸ و ۱۶ و ۳۲ بیت را به خود اختصاص می دهند. ۸ بیت دوم، با شماره مشخص شده ۱ و دو ۸ بیت شماره های ۲ و ۳ و چهار بیت شماره های ۴ تا ۷ به ترتیب برای این عضوها هستند.

هر سه این ساختارها در لینوکس در فایل سرآیند intet/in.h شناسانده شده اند.

در برنامه نویسی شبکه و سوکت ها، باید متغیری از گونه sockaddr_in بسازیم. یک اشاره گر به struct sockaddr_in را می توان به sockaddr تبدیل (Cast) کرد و وارونه این نیز شدنی است. از این رو در تابعی مانند ()connect که یک ورودی اشاره گر به struct sockaddr می خواهد، می توان اشاره گری از گونه struct sockaddr_in فرستاد.

ساختارهای IPv6

لینوکس از نسخه ۲.۲ از IPv6 پشتیبانی را آغاز کرده است و از این رو در ارتباط با آن، ساختارهای struct in6_addr و struct sockaddr_in6 فراهم شده اند. در کد زیر می توانید ببینید که یک سوکت IPv6 با ساختار struct sockaddr_in6 نشان داده می شود که همانند سوکت های IPV4 دارای یک نشانی IP و شماره پورت هستند.آدرس فامیلی برای IPv6 همیشه برابر با AF_INET6 خواهد بود.

struct scokaddr_in6 - struct in6_addr IPv6
struct scokaddr_in6 - struct in6_addr IPv6

در ساختار struct sockaddr_in6 دو عضو به نام های sin6_flowinfo و sin6_scope_id هست که در struct sockaddr_in برای IPv4 نیست. هر آدرس IPv6 دامنه (Scope) ویژه ای دارد که در آن شناسانده شده است. دامنه، یک منطقه توپولوژیکی است که، در آن می توان آدرس IPv6 را به عنوان شناسه ای منحصر (Unique Id) به یک اینترفیس شبکه یا گروهی ای از اینترفیس های شبکه به کار برد. IPv6 از گونه های گوناگونی به نام unicast و multicast و anycast پشتیبانی می کند.

گونه Unicast برای آدرس دهی یک تک میزبان (Host)، گونه Multicast برای آدرس دهی یک گروه از میزبان ها و Anycast برای آدرس دهی نزدیکترین عضو یک گروه از هست که بر پایه راهنمای man ip6 در لینوکس پیاده سازی نشده است. شکل زیر این Scope ها و شناسه (ID) آنها را نشان می دهد.

IPv6 Scope ID and Name
IPv6 Scope ID and Name

آموزش IPv6 بسیار گسترده و بیرون از درون مایه این نوشته است و نوشته های جداگانه ای را می خواهد، از این رو IPv6 را دیگر در این سری از آموزش های برنامه نویسی سوکت نخواهم گفت. در نوشته پَسین درباره Address Family و Socket Type ها خواهم گفت.

شاد و پیروز و تندرست باشید