سرجمع (checksum) روشی است که در بسیاری از پروتکلهای ارتباطی شبکه، از جمله UDP برای تشخیص خطا در انتقال دادهها مورد استفاده قرار میگیرد. در این نوشته با مثال و به شکل عملی نحوهٔ محاسبهٔ چکسام بستههای UDP توضیح داده میشود.
پروتکل User Datagram Protocol یا UDP یک پروتکل لایهٔ انتقال بر بستر پروتکل IP است. این پروتکل بسیار ساده است و بر خلاف همتای خود TCP، تسهیلاتی برای کنترل ازدحام، کنترل جریان و انتقال دادهٔ قابل اتکا ارائه نمیدهد. این مسأله UDP را برای برخی کاربردهای خاص مثل استریم کردن مدیا و بعضی از انواع پیامهای کنترلی شبکه مناسب میکند. نسلهای جدید HTTP نیز که پروتکل عادی مشاهدهٔ صفحات وب است بر مبنای UDP در حال توسعهاند.
هر بستهٔ UDP شامل یک سرآیند یا header و یک بخش داده یا data است. UDP سرآیند سادهای دارد که شامل پورت مبدأ، پورت مقصد، طول بسته و checksum است.
هرچند UDP تسهیلاتی برای انتقال قابلاتکای دادهها ارائه نمیدهد، سعی شده است تا با checksum وجود خطا در بستههای منتقلشده با احتمال بالایی قابل تشخیص باشد.
در UDP، checksum عددی ۱۶بیتی است که از مکمل ۱ گرفتن از جمع ۱۶بیتی سرآیند UDP، دادهٔ UDP و شبهسرآیند IP بسته حاصل میشود. نحوهٔ محاسبهٔ UDP checksum در RFC 768 توضیح داده شده است.
اگر طول بخش داده مضرب صحیحی از ۱۶ بیت نباشد، هنگام محاسبهٔ checksum از انتها با تعداد مناسبی بیت ۰ پر میشود تا طولش به مضربی صحیح از ۱۶ بیت برسد.
شبهسرآیند (psuedo-header) لایهٔ IP با سرآیند واقعی لایهٔ IP متفاوت است.
ساختار شبهسرآیند لایهٔ آیپی با توجه به نسخهٔ پروتکل لایهٔ شبکه تعیین میشود. این شبهسرآیند برای بستر IP نسخهٔ ۴ در RFC 768 و برای بستر IP نسخهٔ ۶ در RFC 2460 توضیح داده شده است و به طور کلی شامل IP و پورت مبدأ و مقصد، طول بستهٔ پیام لایهٔ بالاتر (در اینجا UDP) و کد پروتکل لایهٔ بالاتر (17 یا 0x11
برای UDP) میشود.
توجه کنید که هرچند سرآیند UDP از پروتکل لایهٔ شبکه مستقل است و فقط دربرگیرندهٔ پورت مبدأ و مقصد است، اما پروتکل لایهٔ شبکه و IP مبدأ و مقصد در محاسبهٔ checksum مؤثر است. به دلیل عدم وابستگی به نحوهٔ پیادهسازی لایهٔ زیرین (شبکه)، کپسولهسازی و معماری لایهای شبکه نقض نمیشود.
همچنین قابل ذکر است که طول بستهٔ UDP به بایت (شامل داده و سرآیند UDP اما بدون شبهسرآیند IP) دو بار (یک بار در سرآیند UDP و یک بار در شبهسرآیند IP) در محاسبهٔ checksum جمع میشود.
پس از دریافت یک بستهٔ UDP، گیرنده با افزودن شبهسرآیند IP جمع ۱۶بیتی کل بسته را (شامل checksum) محاسبه میکند. اگر بسته درست باشد این جمع برابر رشتهای ۱۶بیتی از ۱ها (65535) میگردد؛ در غیر این صورت بسته نامعتبر در نظر گرفته میشود.
در این نوشته از ابزار nc
و سیستمعامل لینوکس برای ارسال و دریافت پیام بر بستر UDP و از ابزار wireshark برای تحلیل بستهها استفاده میشود. استفاده از هر ابزار مشابهی برای این هدف امکانپذیر است.
برای ایجاد یک پردازه که بر روی پورت 8080 UDP به اتصالات جدید گوش دهد از دستور زیر استفاده میکنیم:
nc -u -l 8080
سپس ضمن ثبت بستهها با wireshark، برای ارسال رشتهٔ Hello World به آن پردازه از دستور زیر استفاده میکنیم:
echo -n "Hello World" | nc -u 192.168.1.104 8080
فرض کنید دستگاه مقصد IP برابر 192.168.1.104 دارد و IP فرستنده نیز 192.168.1.201 است.
برای مشاهدهٔ راحتتر بستهها در wireshark میتوان از فیلتری مانند فیلتر زیر استفاده کرد:
ip.src_host == 192.168.1.201 && ip.dst_host == 192.168.1.104 && udp
در wireshark بستهٔ UDP به شکل زیر مشاهده میشود:
0000 96 73 1f 90 00 13 73 74 48 65 6c 6c 6f 20 57 6f .s....stHello Wo 0010 72 6c 64 rld
شبهسرآیند IP نسخهٔ ۴ نیز طبق ساختار بیانشده به شکل زیر خواهد بود:
0000 c0 a8 01 c9 c0 a8 01 68 00 11 00 13 .......h....
در ادامه checksum این بسته را محاسبه خواهیم کرد.
---------------------- ---------- IPv4 Psuedo-header |--------------------| |--------| ||0b1100000010101000|| ||0xc0a8|| Src ||0b0000000111001001|| ||0x01c9|| IP |--------------------| |--------| ||0b1100000010101000|| ||0xc0a8|| Dst ||0b0000000101101000|| ||0x0168|| IP |--------------------| |--------| ||0b0000000000010001|| ||0x0011|| ZERO | Protocol (UDP) |--------------------| |--------| ||0b0000000000010011|| ||0x0013|| UDP Length = 19 Bytes |--------------------| |--------| ---------------------- ---------- UDP Header |--------------------| |--------| ||0b1001011001110011|| ||0x9673|| Src Port |--------------------| |--------| ||0b0001111110010000|| ||0x1f90|| Dst Port |--------------------| |--------| ||0b0000000000010011|| ||0x0013|| UDP Length = 19 Bytes |--------------------| |--------| ||0b0000000000000000|| ||0x0000|| Checksum (EMPTY) |--------------------| |--------| ---------------------- ---------- UDP Data |--------------------| |--------| ||0b0100100001100101|| ||0x4865|| He ||0b0110110001101100|| ||0x6c6c|| ll ||0b0110111100100000|| ||0x6f20|| o ||0b0101011101101111|| ||0x576f|| Wo ||0b0111001001101100|| ||0x726c|| rl ||0b0110010000000000|| ||0x6400|| d | ZERO PADDING |--------------------| |--------| ---------------------- ----------
حاصل جمع این اعداد برابر 298119 یا 0b1001000110010000111
میشود؛ اما محاسبات باید به شکل ۱۶بیتی انجام شود، یعنی بیتهای سربار هفدهم به بعد باید با ۱۶ بیت اول جمع شوند:
0b1001000110010000111 = 298119 => 0b1000110010000111 + 0b 100 ______________________ 0b1000110010001011 = 35979
برای محاسبهٔ checksum باید مکمل ۱ عدد حاصل را محاسبه کنیم؛ یعنی ۰ها را به ۱ و ۱ها را به ۰ تبدیل کنیم:
0b1000110010001011 | Ones' complement -> 0b0111001101110100 = 29556 = 0x7374
بنابراین checksum برابر 0x7374
میشود که مشابه checksum موجود در بسته است.
حال اگر این checksum را در بسته قرار دهیم و جمع ۱۶بیتی آن را حساب کنیم به 65535 میرسیم که نمایش دودویی آن رشتهای ۱۶بیتی از ۱ها است. این مسأله نشان میدهد بستهٔ دریافتی معتبر است.
0b1001111111111111011 => 0b1111111111111011 + 0b 100 ______________________ 0b1111111111111111 = 65535 = 0xffff
برای محاسبه میتوان از قطعهکد پایتون زیر استفاده کرد. توجه کنید که در انتهای بسته یک بایت ۰ اضافه شده است تا طول بسته بر ۱۶ بیت بخشپذیر باشد.
h = "c0 a8 01 c9 c0 a8 01 68 00 11 00 13 96 73 1f 90 00 13 73 74 48 65 6c 6c 6f 20 57 6f 72 6c 64 00 " h_combined = h.replace(" ", "") n = [int("0x%s" % h_combined[i*4:i*4+4], base=16) for i in range(len(h_combined) // 4)] bin(sum(n)) >>> '0b1001111111111111011'
به طور پیشفرض wireshark صحت checksum را بررسی نمیکند. در این صورت در مقابل checksum عبارت validation disabled و unverified نوشته میشود.
برای فعال کردن اعتبارسنجی checksum، روی آن کلیک راست کنید و از زیرمنوی Protocol Preferences گزینهٔ Validate UDP checksum if possible را فعال کنید.
گاهی پشتهٔ شبکهٔ سیستمعامل تصحیح checksum را به عهدهٔ کارت شبکه میگذارد. در این شرایط wireshark پیامهای ارسالی را پیش از آن که به کارت شبکه برسند و checksumـشان اصلاح شود میگیرد و در نتیجه در صورت بررسی checksum پیامها نامتعبر تلقی میشوند. پیامهای ارسالشده به localhost (رابط شبکهٔ lo
) نیز همین مشکل را خواهند داشت. این مسأله به عنوان Checksum Offloading شناخته میشود.
برای بررسی بستهها، بستهها را بین دو ماشین ارسال کنید و آنها را روی ماشین گیرنده تحلیل کنید، یا در صورت پشتیبانی درایور کارت شبکه، Checksum Offloading را روی درایور کارت شبکهٔ خود خاموش کنید.