چای ۱۱: حل مشکل نگاشت پورت‌های WSL 2


مقدمه

‏Windows Subsystem for Linux یا به اختصار WSL به توسعه‌دهندگان اجازه میده تا یک محیط GNU/Linux رو بدون سربار ماشین‌های مجازی به‌صورت مستقیم روی ویندوز اجزا کنن. اجرای دستورات و برنامه‌های بر پایه POSIX روی ویندوز ویژگی‌ای‌ست که مایکروسافت مدت زیادی به دنبال آن بوده و بعد از فراگیر شدن روش‌های ایزوله‌سازی و کانتیر کردن سیستم‌ها (containerization) و لزوم به اجرای داکر (Docker) روی ویندوز و در نتیجه تغییر رویکردهای مایکروسافت، به‌صورت جدی‌تر دنبال شد.

نسخه اولیه WSL در سال ۲۰۱۶ به‌عنوان یک لایه رابط کرنل (Kernel Interface) و بدون هیچ کرنل لینوکسی، پس از تلاش‌های ویندوز برای توسعه Hyper-V ظاهر شد که اجازه اجرای یک فضای کاربری* برپایه GNU مانند ابونتو (Ubuntu)‌ را می‌داد [۱].

نسخه دوم WSL در سال ۲۰۱۹ با تغییراتی در معماری (همراه با ادغام Hyper-V داخل ویندوز) معرفی‌شد و یک کرنل واقعی لینوکس است که روی یک ماشین‌مجازی خیلی سبک اجرا می‌شود. در این نسخه، همچنین دسترسی به شبکه و دسترسی مشترک به فضای کاری ویندوز و لینوکس (فایل‌ها و برنامه‌ها) بهبودهای چشمگیری داشته و فرآیند نصب و فعال‌سازی نیز بسیار ساده شده‌‌ست. اخیرا و در آخرین بیلدهای (Build) ویندوز ۱۰ و در ویندوز ۱۱، با معرفیِ پشتیبانی از GPU و همچنین WSLg، برنامه‌های لینوکسی می‌توانند از شتاب‌دهنده‌های سخت‌افزاری بهره‌برده و یا برنامه‌های دارای رابط کاربری گرافیکی (GUI)، در کنار برنامه‌های ویندوزی اجرا شده و قابل دسترس باشند [۱].

می‌توانید شرح تفاوت‌ها و تغییرات بین WSL1 و WSL2 را در مستندات WSL بخوانید.


برای تجربه WSL اگر از نسخه‌های بروز ویندوز ۱۰ و یا ویندوز ۱۱ استفاده می‌کنید، ابتدا از دستور زیر برای راه‌اندازی کرنل استفاده کنین و سپس از Microsoft Store یک توزیع لینوکسی از بین توزیع‌های موجود را نصب کنید.
همین!

wsl --install




مشکل نگاشت پورت‌ها (Port Forwarding)

در کنار ویژگی‌های بی‌نظیر WSL و انعطاف‌پذیری و دسترسی‌پذیری‌ای که برای توسعه‌دهندگان بوجود میاره، مشکلات جزئی* کمی هم داره که یکی از اون‌ها مشکلاتی در مسیریابی پورت‌ها به‌خصوص هنگام دسترسی به سرور‌های محلی (Local Servers) اجرا شده، از سمت ویندوز است.

با اجرای یک برنامه‌ی تحت شبکه، برای مثال اجرای یک سرور روی WSL، باید بتوان از سمت ویندوز و از طریق شبکه داخلی (localhost) به آن‌ها دسترسی داشت (شبکه باید یک شبکه خصوصی [Private] باشد). این اتفاق به‌صورت خودکار در WSL1 انجام می‌شد، اما متأسفانه رفتار پیش‌فرض در WSL2 این نیست [۳].

از آنجایی که WSL2 از یک آداپتور مجازی (Virtual Network Adapter) برای اتصال به شبکه استفاده می‌کند، با هربار ورود به ویندوز و اجرای اولیه WSL، کرنل یک IP جدید در شبکه داخلی، مانند IP زیر دریافت می‌کند [۳]:

❯ hostname -I
172.31.6.171

سرور‌های اجرا شده در WSL از این آدرس در دسترس هستند،‌ اما همانطور که اشاره شد این آدرس هربار تغییر کرده و نیاز داریم تا با استفاده از localhost ویندوز، مانند آدرس زیر، بتوانیم به آن‌ها دسترسی داشته باشیم.

localhost:8080

این مشکل در هربار بوت شدن ویندوز اتفاق نمی‌افتد؛ اما با این حال به دلیل اینکه حتی با ری‌استارت کردن WSL2 و یا خاموش کردن فایروال (Firewall) ویندوز این مشکل رفع نمی‌شود، باعث می‌شود در کارکرد WSL2 عدم قطعیت ایجاد کرده و آن را مختل کند [۲].

یکی از دلایل احتمالی این اتفاق -همانطور که در این issue به آن اشاره شده- به‌دلیل روشن‌بودن تنظیم Fast Startup است، با این‌حال به‌دلیل اینکه شخصاً علاقه‌ای به خاموش‌کردن این گزینه نداشتم، به دنبال راه‌حل دیگری بودم.


لازم به ذکر است می‌توانید برای خاموش‌کردن WSL، دستور زیر را در CMD یا PowerShell ویندوز اجرا کنید:

# PowerShell 
PS C:\Users\[username]> wsl --shutdown




رفع مشکل نگاشت پورت‌ها

برای رفع این مشکل، باید همانطور که در مستندات WSL توضیح داده شده، پورت‌های لازم را به WSL نگاشت یا فوروارد کنیم (Port Forwarding) که برای سهولت کار و خودکار‌سازی آن، از ابزار netsh استفاده می‌کنیم.

برای اینکار یک فایل با فرمت ps1 (برای مثال wsl2-port-forwarding.ps1) ساخته و اسکریپت زیر را در آن کپی کنید [۲ و ۳]:

در صورت عدم نمایش: gist.github.com/mhsattarian/bb55026b1e34cf8357c37289d693cec4
https://gist.github.com/mhsattarian/bb55026b1e34cf8357c37289d693cec4


این اسکریپت برای اجرا نیاز به دسترسی Admin داشته (به‌اصطلاح Elevated Permissions) و در ابتدا برای دریافت این دسترسی‌ها درخواستی فراخوانی می‌کند. سپس آدرس IP فعلی WSL2 را دریافت کرده و با استفاده از دستور netsh پورت‌های مشخص‌شده در آرایه ports$ را به این آدرس فوروارد (نگاشت) می‌کند. در کنار این کار قوانین نامرتبط در فایروال را نیز حذف می‌کند [۲ و ۳].

می‌توانید با تغییر آرایه ports$، پورت‌های دلخواه خودتون رو مشخص کنید.

بدین‌صورت برنامه‌ها و سرورهای اجرا شده در WSL2 از طریق localhost در ویندوز قابل دسترسی خواهند بود. در نهایت می‌توانید برای مشاهده پورت‌های فوروارد شده از دستور زیر استفاده کنید [۳]:

netsh interface portproxy show v4tov4




اجرای خودکار اسکریپت

با هربار اجرای اسکریپت بالا تنظیمات نگاشت پورت‌ها به درستی تنظیم (Set) شده و برنامه‌ها و سرور‌های WSL2 از طریق localhost در ویندوز در دسترس خواهند بود. بااین‌حال، اجرای اسکریپت هربار که WSL به مشکل می‌خورد شاید چندان راحت یا مقبول نباشد؛ برای سهولت بیشتر می‌توان با استفاده از Task Scheduler ویندوز این اسکریپت را در هربار بوت سیستم اجرا کنیم [۲].

برای اینکار وارد Task Scheduler شده و از منوی سمت راست روی Create Task کلیک کنید؛ در پنجره‌ی باز شده یک نام برای این تسک مشخص کرده و وارد تب Triggers شوید، روی گزینه New کلیک کرده و زمان اجرای تسک را برابر At startup قرار دهید. پیشنهاد میشه برای تأخیرِ (Delay) اجرای تسک نیز مقداری (مثلاً ۳۰ ثانیه) را مشخص کنید:


مشخص کردن زمان اجرای تسک در تب Triggers
مشخص کردن زمان اجرای تسک در تب Triggers


سپس وارد تب Actions شده و روی گزینه New کلیک کنید. به‌عنوان برنامه‌ای که باید اجرا شود به‌صورت زیر PowerShell ویندوز را مشخص کنید:

Powershell.exe

به‌عنوان آرگومان‌ها به‌صورت زیر، آدرس اسکریپت را به‌همراه فلگ ExecutionPolicy- با مقدار Bypass وارد کنید:

C:\Users\[username]\[path_to_script]\wsl2-port-forwarding.ps1 -ExecutionPolicy Bypass


مشخص کردن برنامه اجرا شده در تب Actions
مشخص کردن برنامه اجرا شده در تب Actions


اگر از لپتاپ استفاده می‌کنید و در صورت امکان در تب Conditions تیک گزینه زیر را بردارید تا تسک زمانی که لپتاپ درحال شارژ نیست نیز اجرا شود:


تنظیم اجرای تسک در زمان استفاده از باتری داخلی لپتاپ
تنظیم اجرای تسک در زمان استفاده از باتری داخلی لپتاپ


با زدن گزینه‌ی OK این تسک را ذخیره کنید. بدین‌صورت با هربار بوت ویندوز این تسک اجرا شده و دیگر نیازی به اجرای دستی آن نیست.



پانویس

*فضای کاربری: یک سیستم‌عامل مدرن، معمولاً حافظه‌ی مجازی را به دو بخش فضای هسته و فضای کاربری مجزا می‌کند. در اینجا منظور از فضای کاربری یک سیستم عامل لنوکسی (تعدیل شده) بدون کرنل لینوکس است.

*مشکلات جزئی WSL: دیگر مشکلاتی که برخوردم شامل، اجرا نشدن WSL بعد از فراخوانی‌ست که با ری‌استارت WSL درست شده و مشکل دیگر آزاد نشدن رم اشغال شده توسط WSL پس از پایان یک پردازش (Process) است که طبق وعده‌های مایکروسافت در آپدیت‌های آینده باید رفع شود.


منابع

[۱] صفحه ویکی‌پدیا Windows Subsystem for Linux

[۲] کامنت‌های issueهای ریپازیتوری WSL در گیت‌هاب (Github)

[۳] این پست با موضوع مشابه در dev.to



این پست، قسمت یازدهم از چای، مجموعه‌ای در باب «چیزی که امروز یادگرفتم» است. باقی چای‌ها رو می‌تونید از اینجا مشاهده کنید و در مورد فلسفه‌ی این کار بخونید.