گفتیم که فرمت هدر در پروتکل های BOOTP و DHCP یکسان هست. پس چگونه این دو از هم تشخیص داده میشن؟ سرور DHCP و همینطور نرم افزارهای شنود مثل wireshark) با استفاده از مفهوم magic cookie تشخیص میدن که پیام رسیده از نوع BOOTP هست یا DHCP. بدین صورت که اگر 4 بایت ابتدایی در قسمت Options با اعداد ثابت 99.130.83.99 یا 0x63825363 پر شده باشن؛ بدین معنی است که پیام ارسالی مبتنی بر DHCP است وگرنه داریم از BOOTP استفاده میکنیم. گزینه های مورد نیازمون هم بعد از 4 بایت magic cookie نوشته میشن.
مهمترین مورد در بخش گزینهها؛ گزينه شما 53 با نام “Message Type” يا همان نوع پيام است. اين گزينه فقط يك بايت داده داره در نتيجه 3 بايت اشغال ميكنه و بهصورت 0x35,0x01,0x– استفاده ميشه كه 0x– نوع پيام رو مشخص ميكنه و شامل موارد زير هست:
موارد ديگه اي هم برای نوع پیام هست كه اينجا اعلام نشد (RFC 2132). در مرحله Discovery کلاینت این گزینه رو بهصورت 0x35,0x01,0x01 میفرسته؛ در مرحله دوم سرور 0x035,0x01,0x02 و…
گزينه مهم ديگه، گزينه شماره 50 با نام Requested IP Address هست. اين گزينه 4 بايت داده براي IP مورد تقاضا (ترجیحی) هست و در نتيجه 6 بايت رو اشغال ميكنه كه دو بايت ابتدايي اون 0x32,0x04 و 4 بايت بعدي آدرس IP مورد تقاضا رو نشون ميده.
از گزینههای معروف ديگه ميشه به گزينه 23 جهت مشخص نمودن مقدار پیشفرض TTL در يك ارتباط مبتني بر IP ؛ گزينه شماره 6 براي تعيين سرور DNS؛ گزينه شماره 51 جهت تعيين زمان اجاره و يا گزينه 52 براي وقتي كه مقدار بایتهای بخش آپشن، جهت قراردادن گزینههای ارسالي كافي نباشه. با اين گزينه، گيرنده متوجه ميشه كه بخشهای SName و File نيز دربرگيرنده بخشي از گزینهها هستند. برای اطلاعات بیشتر در مورد گزینههای پروتکل DHCP به سند RFC 2132 مراجعه کنید.
يه مرور كوچولو بكنيم. ابتدا كلاينت روشن ميشه (و برای سادگی، فرض میکنیم كه كابل شبكه هم متصله و همه چی گل و بلبله) كلاينت كه فقط مك آدرس خودش رو میدونه؛ يك پيغام عمومي در لايه دو و سه شبكه (مك آدرس مقصد FF:FF:FF:FF:FF:FF ؛ آیپی آدرس مقصد 255.255.255.255؛ آدرس آیپی مبدأ 0.0.0.0) با پورت مبدأ 68 و پورت مقصد 67 روي شبكه ميفرسته. بیت B هم ست میشه. در جواب، سرور كه الان مك آدرس كلاينت رو داره؛ يك پيغام خصوصي در لايه دوم (با مك آدرس گيرنده يعني كلاينت) و عمومي در لايه سوم (آیپی 255.255.255.255) با پورت مبدأ 67 و مقصد 68 ميفرسته. در مرحله سوم، كلاينت از بين پاسخهای دريافتي يكي رو انتخاب ميكنه (در استاندارد، مواردی مثل زمان انتظار براي دريافت پيشنهاد و… ذكر شده) و حالا؛ كه هم مك آدرس و هم آیپی آدرس سرور رو داره؛ یک پيغام خصوصي به سرور ميفرسته و در نهايت، در مرحله چهارم، سرور با تأیید اين عمليات بهصورت خصوصي يا عمومي (باتوجهبه بيت B در مرحله سوم) كار رو تموم ميكنه.
خب بریم سراغ کد؛ ما یه نرمافزار دانلود کردیم تا بتونیم ویندوز رو بهصورت یک DHCP server فعال کنیم. در ابتدای برنامه؛ بعد از پیکربندی تراشه ENC (و احتمالاً در یک حلقه بینهایت) ما باید فرایند دریافت آدرس IP رو باید انجام بدیم تا بعد از دریافت آدرس IP بتوانیم عملکرد عادی برد رو داشته باشیم. کدی که در میکروکنترلر نوشته شده؛ اینطوری طراحی شده که پس از دریافت یک پیام روی UDP؛ در صورت نیاز، بهش جواب میده. پیام مرحله 3 رو که در جواب پیام مرحله 2 میتونیم بفرستیم. پیام مرحله چهار هم که نیاز به جواب نداره. میمونه پیام مرحله اول که با این روش قابلارسال نیست. پس ما باید یک فریم حامل پیغامی مبتنی بر DHCP/UDP/IP/Ethernt ii ایجاد و توسط تابع ENC28J60_TransmitFrame ارسال کنیم تا DHCP server رو در جریان خواسته خودمون قرار بدیم. طبیعیه که باید یک روتین مناسب برای این عملیات نوشته بشه. اما جهت درک سادهتر مفهوم ارسال درخواست DHCP Dicovery؛ این قسمت از کد رو بهصورت بسیار سادهای پیادهسازی کردیم. بدین ترتیب که یک فریم Ethernet ii رو طوری چیدمان کردیم که پروتکلهای DHCP,UDP و IP رو شامل باشه. این کد بهصورت زیر در فایل main.c و بعد از راهاندازی اولیه ENC توسط تابع ENC28j60_init پیادهسازی شده:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | ENC28J60_Init(); // ایجاد هدر لایه دوم // //در این قسمت مک آدرس مقصد رو عمومی اعلام کردیم و مک آدرس خودمون رو هم بهعنوان مبدأ نوشتیم// //Destination MAC Address ff:ff:ff:ff:ff:ff frame.data[0]=0xff;frame.data[1]=0xff;frame.data[2]=0xff;frame.data[3]=0xff;frame.data[4]=0xff;frame.data[5]=0xff; //Source MAC Address, for example 00:17:22:ed:a5:01 // or copy from enc28j60.c file frame.data[6]=0x00;frame.data[7]=0x17;frame.data[8]=0x22;frame.data[9]=0xED; frame.data[10]=0xA5;frame.data[11]=0x01; //پروتکل مد نظرمون در لایه 3 رو آیپی معرفی کردیم// frame.data[12]=0x08; frame.data[13]=0x00; //IP protocol=0x0800 //حالا هدر پروتکل آیپی رو ست کردیم// // IP Header frame.data[14]=0x45; //VER=4, HLen=5 frame.data[15]=0x00; //TOS=0x00 frame.data[16]=0x01; frame.data[17]=0x1A; // total length of IP packet=UDP+20=282 bytes frame.data[18]=0x00; frame.data[19]=0x00; // ID=0x0000 frame.data[20]=0x40; frame.data[21]=0x00; // Flag=0b010,Offset=0x00; frame.data[22]=0x40; // TTL=64 frame.data[23]=0x11; // UDP =0x11 پروتکل //مقدار چک سام آیپی رو قرار میدیم؛ پایین کد نوشتیم که چطور حساب کردیم// // CheckSum for IP packet frame.data[24]=0x39; frame.data[25]=0xd4; //آدرس IP خودمون رو هنوز نداریم و جاش صفر میذاریم، آدرس آیپی مقصد رو هم نمیدونیم و عمومی ارسال میکنیم// frame.data[26]=0x00;frame.data[27]=0x00;frame.data[28]=0x00;frame.data[29]=0x00; //SA frame.data[30]=255;frame.data[31]=255;frame.data[32]=255;frame.data[33]=255; //DA //حالا هدر پروتکل UDP رو میچینیم// ///// UDP Header frame.data[34]=0x00; frame.data[35]=0x44; //Source port=68 frame.data[36]=0x00; frame.data[37]=0x43; //Destination port=67 //اینجا مقدار طول سگمنت رو نوشتیم!// frame.data[38]=0x01; frame.data[39]=0x06; // UDP data length+8=254+8=262=0x0106 //مقدار چک سام UDP رو هم اینجا مینویسم. طریقه محاسبه چک سام در پایین کد گفته شده// // CheckSum for UDP segment frame.data[40]=0x48; frame.data[41]=0xD5; //حالا هدر پروتکل DHCP // // DHCP Header frame.data[42]=0x01; //operation Request=1 frame.data[43]=0x01; //HType ethernet=1 frame.data[44]=0x06; //MAC Address Length=6 frame.data[45]=0x00; //hops=0 //XID = 0xAABBCCDD frame.data[46]=0xAA;frame.data[47]=0xBB;frame.data[48]=0xCC;frame.data[49]=0xDD; frame.data[50]=0x00;frame.data[51]=0x00; //SECs=0 frame.data[52]=0x80;frame.data[53]=0x00; //B bit=1 frame.data[54]=0x00;frame.data[55]=0x00;frame.data[56]=0x00;frame.data[57]=0x00;//ciaddr=0 frame.data[58]=0x00;frame.data[59]=0x00;frame.data[60]=0x00;frame.data[61]=0x00;//yiaddr=0 frame.data[62]=0x00;frame.data[63]=0x00;frame.data[64]=0x00;frame.data[65]=0x00;//siaddr=0 frame.data[66]=0x00;frame.data[67]=0x00;frame.data[68]=0x00;frame.data[69]=0x00;//giaddr=0 ///MAC Address 16bytes(octet) frame.data[70]=0x00;frame.data[71]=0x17;frame.data[72]=0x22;frame.data[73]=0xED;//chaddr frame.data[74]=0xA5;frame.data[75]=0x01;frame.data[76]=0x00;frame.data[77]=0x00;//chaddr frame.data[78]=0x00;frame.data[79]=0x00;frame.data[80]=0x00;frame.data[81]=0x00;//chaddr frame.data[82]=0x00;frame.data[83]=0x00;frame.data[84]=0x00;frame.data[85]=0x00;//chaddr // 192 بایت 0x00 برای بخش های sname,file // //192 bytes(sname,file) =0x00 for(int k=0;k<192;k++) {frame.data[86+k]=0x00;} // تنظیم مقدار magic cookie // frame.data[278]=0x63; frame.data[279]=0x82; frame.data[280]=0x53; frame.data[281]=0x63 //تنها گزینه ارسالی؛ گزینه شماره 53 با مقدار 0x01 به معنای پیام دیسکاوری // frame.data[282]=0x35;frame.data[283]=0x01;frame.data[284]=0x01; // پدینگ و بستن انتهای پیام // for(int k=285;k<295;k++) {frame.data[k]=0x00;} frame.data[295]=0xff; // end of option list // و در نهایت ارسال پیام // ENC28J60_TransmitFrame(&frame.data[0],296); |
1 | uint8_t ipAddr[IP_ADDRESS_BYTES_NUM] = {255, 255, 255, 255}; |
برای پیغام مرحله سوم (Request) در پاسخ Offer هم از کدی که تا الان داشتیم؛ استفاده کردیم. چون آیپی برد ما در حال حاضر 255.255.255.255 هست؛ پس کدی که الان در اختیار داریم؛ پکت های عمومی آیپی رو دریافت میکنه و چون پیغام روی UDP ارسال میشه، کافیه در تابع UDP_Process تغییرات لازم رو اعمال کنیم. اولین قدم اینه که بررسی کنیم آیا روی پورت 68 چیزی اومده یا نه؟
1 | else if(destPort == UDP_DHCP_PORT) //DHCP client=68 |
گام بعدی اینه که ببینیم آیا پیغام فعلی پیغام Offer یا ACK هست. برای این کار در ابتدای بخش گزینهها، اول باید مجیک کوکی رو پیدا کنیم و بعد اگر مقدار موجود در گزینه 53، عدد 2 بود (یعنی پیغام Offer) باید پاسخ پیام که همون پیغام Request هست رو بفرستیم. مقدار آیپی واقعی رو هم از بخش yiaddr به دست میاریم و بهعنوان آدرس IP خودمون ذخیره میکنیم. البته بهتر بود بعد از دریافت Ack انجام بشه.
1 2 3 | ipAddr[0]=udpFrame->data[16]; ipAddr[1]=udpFrame->data[17]; ipAddr[2]=udpFrame->data[18]; ipAddr[3]=udpFrame->data[19]; |
برای تست؛ آدرس IP کامپیوتر رو 192.168.30.1 قرار دادیم و محدوده مجاز برای پیشنهاد آدرس رو هم 192.168.30.2 تا 192.168.30.255 ست کردیم. بریم ببینیم wireshark این پیغام ها رو چطور ثبت کرده:
پیغام Discover فرستاده شده توسط کلاینت (میکروکنترلر):
پیغام Offer ارسال شده توسط سرور:
پیغام Request که میکروکنترلر در پاسخ به Offer به سرور برگردونده:
و در نهایت پیغام Ack توسط سرور:
بعد از عملیات هم اگر پنجره برنامه DHCP sever رو باز کنید (کلیک راست روی آیکن نرمافزار در تسک بار ویندوز و انتخاب open status) باید همچین چیزی ببینید:
بعد از اتمام پروسه تخصیص آدرس IP؛ با دستور پینگ؛ تنظیمشدن آدرس آیپی رو چک کنید.
DNS و DHCP چیست؟ - آموزش کامل با...
نویسنده شو !
سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.