port punching/hole punching چیست!

این رو توی وبلاگ کسی سوال کرده بود ولی چون نسبتا مفصل هست و توی کامنت ها جای مناسبی برای این کار نیست گفتم اینجا مطرح میکنم بیاد.

خب من با زبان ساده و بصورت عملی با مثال براتون توضیح میدم، البته بعضی جزییات خیلی ریز فرعی رو ممکنه فراموش کرده باشم ولی کلیتش بقدر کافی روشنه.

فرض کنید سیستم A و سیستم B هر دو از طریق مودم ADSL به اینترنت متصل هستن.
خب حالا سیستم B میخواد با سیستم A ارتباط مستقیم (P2P) برقرار کنه. الان باس چکار کنه؟
آفرین خرگوش باهوش!
اول باید IP سیستم A رو گیر بیاره. خب از کجا گیر بیاره؟ پس ما نیاز به یک سیستم واسط داریم که هر دو سیستم یجوری بشناسن و بتونن باهاش ارتباط برقرار کنن و IP خودشون رو بهش بدن و IP سیستم دیگر رو ازش دریافت کنن، و بعد از اینکه IP سیستم دیگر رو بدست آوردن میتونن باهاش ارتباط مستقیم برقرار کنن.
اما متاسفانه در عمل مسئله از این هم پیچیده تره. فرض کنید سیستم B آدرس IP سیستم A رو هم داشت و یک پکت UDP بهش فرستاد، حالا چی میشه؟ پکت میاد پشت درب مودم میگه تق تق، مودم میگه برو گم شو!!
مودم این پکت رو نادیده میگیره، چرا؟ خب این مسئله هم جنبه های امنیتی داره و هم بخاطر مسئلهء دیگری. اولا که اغلب مودم ها و فایروال ها، اجازهء ورود پکت های IP که از اینترنت بی در و پیکر میان رو نمیدن. مثلا شما میتونید در مرورگر خودتون یک وبسایت در اینترنت رو باز کنید، ولی در این حالت اول سیستم شماست که پکت هایی رو به آدرس IP سایت مورد نظر ارسال میکنه، مودم که وسیله و رابط این ارتباط است این مسئله رو تحت نظر داره و آدرس IP و پورتی رو که این پکت ها بهش ارسال میشن رو بیاد میسپاره و بنابراین وقتی پکت هایی از اون آدرس و پورت به آدرس IP خودش ارسال میشن به اونا اجازهء ورود میده چون میدونه که این قبلا سیستم خود شما بوده که با اون سیستم ارتباط برقرار کرده.
البته میشه مودم رو بصورت دستی تنظیم کرد که اجازهء ورود پکت ها با مشخصات خاصی (مثلا پورت هدف خاصی) رو بده، ولی این کار رو معمولا تا میتونن از روش دیگه استفاده کنن نمیکنن چون دردسر داره و نیاز به دخالت مستقیم کاربر هست و باید دستی با ورود به کنترل پنل و تنظیمات مودم انجام بشه و کار فنی است و ممکنه برای کاربر مشکل باشه یا دچار اشتباه بشه، گذشته از تبعات امنیتی که ممکنه داشته باشه.
و اما دلیل دومی که مودم پکت هایی رو که از بیرون میان به پشت خودش یعنی شبکهء داخلی شما راه نمیده اینه که ممکنه چند سیستم به مودم شما وصل باشن که هرکدام آدرس IP شبکهء داخلی خودشون رو دارن (مثلا 192.168.1.21 و 192.168.1.33 و …)، خب الان اینجا مودم چطوری بفهمه این بسته رو باید به کدوم یک از این سیستم ها/IP ها تحویل بده؟ حتی اونی هم که اون بسته رو فرستاده طبیعتا نمیدونسته که آدرس IP سیستم مورد نظر در شبکهء داخلی شما چیه!
پس نتیجه میگیریم که ارتباط P2P تنها وقتی امکان پذیر است که هر دوی سیستمهایی که میخوان با هم ارتباط مستقیم برقرار کنن، قبلا پکت هایی رو به آدرس IP و شماره پورت همدیگر ارسال کرده باشن. بنابراین سیستم ها باید ابتدا آدرس IP و شماره پورت سوکت برنامهء طرف مقابل رو بدست بیارن، بعد هر دو همزمان شروع میکنن به ارسال پکت هایی به آدرس IP و شماره Port طرف مقابل، که در نتیجه در نهایت پکت های هر دو طرف از مودم ها عبور میکنه و اتصال بین دو برنامه برقرار میشه. البته اگر مدتی پکتی به این پورت ها ارسال نشه، مودم یا فایروال مجددا اون پورت رو مسدود میکنه، بنابراین اینطور برنامه ها معمولا حتی اگر دیتایی برای تبادل نداشته باشن ولی میان و مثلا هر 10 ثانیه یا 20 ثانیه هرچی خلاصه پکتی رو به همدیگر ارسال میکنن تا پورت ها بسته نشن و نیازی به تکرار عملیات اتصال اولیه که مقداری زمانبر هم هست نباشه.
خب به این ترفند میگن port punching/hole punching و این حرفا چون مثل این میمونه که دو طرف برای ارتباط، در مودم یا فایروال حفره ای رو ایجاد میکنن.
روشن بود؟

————————————————–

خب پس مشخصه که برقراری ارتباط P2P پیچیدگی و مشکلاتی داره. مگر اینکه از کتابخانه ای چیزی استفاده کنیم که این کارها رو واسمون راحت کنه، وگرنه مجبوریم خودمون کلی کد بنویسیم و تست کنیم. خلاصه دردسر زیاد داره!
شما باید یه سرور واسط استفاده کنید واسه اینکه Public IP و شماره پورت سوکت هر طرف رو بدست بیارید، بعد این آدرسها باید بین دو طرف تبادل بشن، یعنی یه الگوریتم باید باشه که هر طرف از اینکه طرف دیگر میخواد باهاش ارتباط برقرار کنه، از قبل آگاه بشه، بعد هر دو طرف باید همزمان برای اتصال تلاش کنن. همهء این دردسرها بخاطر سر راه بودن مودم یا فایروال هست که توضیح دادم، وگرنه مثلا توی اینترنت شما میخواید از یک وب سرور با وب سرور دیگری تماس بگیرید این دنگ و فنگ رو نداره و مستقیم و بدون مقدمه میشه تماس گرفت (فقط کافیه برنامهء مورد نظر روی سرور هدف در حال اجرا باشه و روی یک پورت از قبل تعیین شده در حال گوش دادن و منتظر دریافت دیتا باشه).
یه سوال دیگه اینکه ما اصلا چرا از UDP استفاده کردیم چرا از TCP استفاده نکردیم؟
البته شاید از TCP هم بشه استفاده کرد، که در این حالت کارمون خیلی راحت تر میشه، ولی من فکر کردم این کار به این راحتی ها نباشه، میدونید چرا؟ چون TCP پروتکل پیچیده تر و سطح بالاتریه، و ما در برنامه نویسی خودمون مستقیما packet های TCP رو ارسال نمیکنیم بلکه از کتابخانه های واسط برای این کار استفاده میکنیم. حالا موضوع اینه که این کتابخانه ها معمولا امکاناتی برای hole punching و این حرفا ندارن، پس چطور میشه hole punching رو با اونا بدست آورد؟ مسائل و جزییات و مشکلات متعددی ممکنه در این راه باشه. به این دلیل من از UDP استفاده کردم چون هر ارسال UDP کاملا مستقل و تحت کنترل خودمونه و بنابراین براحتی میتونیم hole punching رو درش پیاده سازی کنیم، ولی حالا این برنامه هایی که من نوشتم فقط واسه تست بود و توی یه برنامهء واقعی شما نیاز به کدنویسی و تمهیدات خیلی بیشتری با UDP دارید، چون UDP بسته ها رو ارسال میکنه و ول میکنه به امکان خدا، یعنی اینکه فلان دیتا رو شما با UDP ارسال کردید، به دلایل مختلف ممکنه اصلا به مقصد نرسه، ممکنه ارسال 2 قبل از ارسال 1 به مقصد برسه، ممکنه ارسال بصورت تکراری (دو بار سه بار…) به مقصد برسه، و بخاطر همین توی برنامه باید کدها و تشکیلات کامل و دقیقی بنویسید که تمام این موارد رو بتونه چک و شناسایی بکنه و فرضا دیتاهای ارسالی رو مرتب کنه، ارسال های تکراری رو شناسایی و حذف بکنه، بخش هایی که دریافت نشدن رو به اطلاع طرف مقابل برسونه و درخواست ارسال مجدد اونها رو بکنه، … و دست آخر مثلا اگر چندین بار تلاش کرد و موفق نشد هست که باید خطای شبکه اینا بده، خلاصه کم داستان نداره! ولی پروتکل TCP خودش پشت صحنه همهء این کارها رو واسمون انجام میده، فقط بخشی که درموردش ابهام دارم و بنظرم مشکل باشه اینه که چطور اتصال اولیه رو به روش hole punching با کتابخانه های موجود میشه انجام داد. بهرحال من تاحالا نیازی نداشتم و بخاطر همین بیشتر از این در این موضوع تحقیق و بررسی و تست نکردم، شاید هم زیاد سخت نباشه. موضوع اینه مشخصات بسته هایی که کتابخانه ای که ازش ارسال میکنید ارسال میکنه با مشخصات بسته هایی که شما توی کد مستقیما ارسال میکنید تفاوت میکنه، مثلا شماره پورتشون متفاوت است، یا همچنین یکسری sequence number و این حرفا که در بسته های TCP باید مطابق ترتیب خاصی باشن تا سیستم اونها رو معتبر بدونه و جزو یک ارتباط واحد تلقی کنه… بنابراین احتمال میدم کارش به مشکل بخوره!

راستی توجه شدید این وسط نقش stun server ها چیه؟ ما میتونیم یک درخواست TCP به یک وب سرور مثلا با PHP بدیم تا بفهمیم که Public IP ما چیه، ولی شماره پورت خارجی سوکت UDP ما با شماره پورتی که درخواست HTTP که بر روی پروتکل TCP ارسال میشه داره یکی نیست. پس ما نمیتونیم شماره پورت خارجی خودمون رو با برقراری ارتباط TCP با سرورهای اشتراکی بدست بیاریم. تنها راهش اینه که با همون سوکت UDP که میخوایم باهاش ارتباط برقرار کنیم با سرور واسط تماس بگیریم، که در این صورت یا باید VPS ای چیزی داشته باشید یا از سرورهای رایگان پروتکل STUN برای این امر استفاده کنید، که بنده برنامه اش رو نوشتم. ولی تا اینجا شما فقط IP و پورت خارجی سوکت خودتون رو بدست آوردید، بعدش مسئله اینه که حضور دو طرف به اطلاع همدیگر برسه و هر دو آدرس و پورت خودشون رو به طرف دیگر برسونن، که این کار رو میشه از طریق یک سرور عادی و مثلا با PHP انجام داد، که بنده همین کار رو کردم.

——————————————————–

خلاصه کار سنگین هست بنظرم و برنامه نویسی سطح پایین؛ کم و بیش سیستمی!
اینطور مواقع میفهمیم که برنامه نویسان واقعی دنیا کیا بودن و هستن. کسانیکه برنامه نویسی سیستمی میکنن و تمام این پروتکل و تشکیلات پایه که ما استفاده میکنیم رو پیاده سازی میکنن، چون واقعا کار سختیه و اطلاعات گسترده و عمیقی نیاز داره (بعضا الگوریتم های پیچیده ای هم داره). خلاصه گفتم یادی بکنیم از مرحومان درگذشته بزرگان و شهداء این رشته!

راستی ممکنه بگید مگه ما نمیتونیم شماره پورت خروجی سوکت برناممون رو همینطور روی سیستم لوکال بدست بیاریم. باید بگم درست روی این فکر و تحقیق نکردم (یا شایدم قبلا کردم ولی جزییاتش یادم نیست) ولی بنظرم اعتباری بهش نیست و به دلایل مختلف این شماره پورت ممکنه روی لوکال شما باشه ولی در اینترنت ارتباط شما با شماره پورت دیگری دیده بشه. ما نیاز به Public IP and port number داریم، یعنی چیزی که نهایتا در اینترنت بسته های IP ما با اون هویت است. خلاصه باید طوری عمل کنیم که مطمئن باشه و در همه شرایطی درست جواب بده، مطمئن ترین راه هم اینه که با یک سرور در اینترنت تماس بگیریم، این مثل اینه که برای فهمیدن شماره تلفن خودتون با گوشی خودتون زنگ بزنید به دوستتون و ازش بپرسید که چه شماره ای روی گوشیش افتاده. سرورهای پروتکل STUN همین کار ساده رو انجام میدن که به شما اطلاع میدن که در اینترنت با چه IP و port ای دیده میشید، ولی همین کار برای ارتباط P2P از پشت NAT ها واجبه و برای اجراش هم نیاز به سرور اختصاصی هست یا استفاده از سرویس های رایگان مخصوص این کار.

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

*

شما می‌توانید از این دستورات HTML استفاده کنید: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>