کارخواه VPN متصل می‌شود، مرورگر کار می‌کند، و تنها فرمانی که واقعاً برایتان مهم است همچنان از اتصال خانگی شما بیرون می‌رود۔ یا عکس آن رخ می‌دهد: تونل مسیر پیش‌فرض را می‌گیرد و کارساز پرونده محلی از میزکار ناپدید می‌شود۔ این را با یک عامل ساخت تجربه کردم که باید به آینه بسته‌های داخلی می‌رسید در حالی که بقیه میزبان روی LAN اداری می‌ماند، و کارخواه دقیقاً دو گزینه پیشنهاد می‌داد: همه ترافیک یا هیچ‌یک۔ درخواست در هر دو جهت یکی است، این برنامه و نه کل دستگاه، و بیشتر کارخواه‌ها هیچ کادر تیکی برای آن ندارند، زیرا جدول مسیریابی هرگز نمی‌دانسته برنامه چیست۔

چرا جدول مسیریابی برنامه شما را نمی‌بیند

وقتی فرایندی بسته‌ای می‌فرستد، پشته نشانی مقصد را در جدول مسیریابی جست‌وجو می‌کند۔ نه نام فایل اجرایی و نه درگاه در آن جست‌وجو وارد نمی‌شوند۔ نام‌های دامنه به همان دلیل شکست می‌خورند: تونل ترافیک را به یک نشانی می‌برد، و تا زمانی که نامی به نشانی تبدیل شود، مسیریابی تنها اهرمی است که هسته هنوز دارد۔ به همین دلیل «VPN فقط برای این سایت» در نهایت به معنای یک واسطه است، و «VPN فقط برای این برنامه» به معنای یکی از مجموعه‌ای کوچک از سازوکارها۔

MechanismWhat selectsCoversOn tunnel drop
Socket bindsource addressa client you controlconnections fail
UID rule (Linux)owning userevery process of one accountconnections fail
Firewall profile (Windows)adapter profileone programfails closed
Network namespacenamespace membershipeverything started insideconnections fail
SOCKS hopapp proxy settingone app or one browser profileconnections fail

هیچ‌یک از این‌ها مسیر به‌ازای فرایند نیست۔ هرکدام تصمیم را به لایه‌ای منتقل می‌کند که برنامه، کاربر یا پیکربندی خود برنامه را می‌بیند۔

یک مقصد تنها همچنان یک مسیر است، نه قاعده برنامه۔ در macOS دستور معمول برداشتن تیک «ارسال همه ترافیک از VPN» و افزودن مسیر میزبان است: sudo route add -host internal-tuner.example -interface ppp0، که دوباره با sudo route delete -host internal-tuner.example -interface ppp0 حذف می‌شود۔ نخست نام واسط را از ifconfig بخوانید، چون ppp0 فقط یک نمونه است۔ نویسه‌های عام در مسیر میزبان کار نمی‌کنند، بنابراین خدمتی که با چند نام سخن می‌گوید برای هر نام یک ورودی می‌خواهد، و قاعده همه فرایندهای دستگاه را پوشش می‌دهد، نه آن یکی که در ذهن داشتید۔

سوکت را به نشانی تونل مقید کنید

وقتی کارخواه در اختیار شماست، مقیدسازی ارزان‌ترین گزینشگر است۔ سوکت مقیدشده به نشانی محلی تونل تا زمانی که مسیری به مقصد آنجا وجود دارد از همان واسط بیرون می‌رود، و بقیه چیزها روی دستگاه مسیر عادی خود را نگه می‌دارند۔ نکته باریک این است که مقیدسازی نشانی مبدأ را برمی‌گزیند در حالی که جدول مسیریابی هنوز خروجی را برمی‌گزیند۔ مقیدسازی به‌تنهایی ترافیکی را که مسیری به تونل ندارد جابه‌جا نمی‌کند، و همین خطاست که پشت بیشتر گزارش‌های «مقیدش کردم و باز هم از راه اشتباه بیرون می‌رود» است۔

import socket
s = socket.socket()
s.bind(("10.8.0.2", 0))
s.connect(("build.internal.example", 443))

ابزارهای سطح بالاتر همان ایده را عرضه می‌کنند: curl --interface 10.8.0.2، CURLOPT_INTERFACE در PHP، و آرگومان source_address در کارخواه‌های HTTP پایتون۔ نشانی باید از آنِ تونل باشد، و یک اشتباه تایپی اینجا به‌صورت خطای اتصال معمولی شکست می‌خورد، نه شکایت مسیریابی۔ مقید کردن به نام دستگاه به‌جای نشانی یک امتیاز در Linux است و به Windows یا macOS منتقل نمی‌شود، از این رو اسکریپت‌های قابل‌حمل نشانی را می‌فرستند۔

Linux: مالک را تطبیق دهید، نه نام برنامه را

هسته می‌تواند کاربر مالک سوکت را تطبیق دهد، بنابراین یک حساب اختصاصی گزینشگر می‌شود۔ تونل را در جدول مسیریابی خودش بگذارید و تنها ترافیک همان حساب را به آنجا بفرستید:

ip rule add uidrange 1001-1001 lookup 100
ip route add default dev tun0 table 100

این فقط وقتی کار می‌کند که تونل از پیش مسیر پیش‌فرض نباشد۔ کارخواه را با --route-nopull آغاز کنید، یا تغییرمسیر هل‌داده‌شده را با --pull-filter ignore "redirect-gateway" روی OpenVPN 2.4 و بالاتر پالایش کنید، سپس خودتان ورودی جدول را بیفزایید۔ وگرنه هر دو پیش‌فرض وجود دارند و آن یکی که اولویت کمتری دارد برنده می‌شود۔ نسخه آگاهانه آن این است که مسیر پیش‌فرض را به درگاه تونل بگردانید، یا این کار را از اسکریپت قطع اتصال انجام دهید: ترافیک را هنگام افتادن تونل از اینترنت دور نگه می‌دارد، اما آن یک کلید قطع است، نه گزینشگر، و تا کسی آن را برندارد می‌ماند۔

نسخه قوی‌تر یک فضای نام شبکه است۔ آن را بسازید، کارخواه را درونش اجرا کنید و برنامه را با ip netns exec آغاز کنید۔ هیچ چیز بیرون از فضای نام واسط تونل را نمی‌بیند، پس برنامه نمی‌تواند به‌طور تصادفی به مسیر میزبان بازگردد۔ هزینه واقعی است: فضای نام برای رسیدن به شبکه فیزیکی به یک جفت veth و پرونده حل‌کننده خودش نیاز دارد، چون فضای نام نصب تنظیمات حل‌وبند میزبان را شریک نمی‌شود۔

دو حالت شکست بیشتر نشت‌ها را توضیح می‌دهند۔ حل نام همچنان در حل‌کننده میزبان رخ می‌دهد، پس پرس‌وجو حتی وقتی اتصال TCP به درون می‌رود از بیرون تونل خارج می‌شود۔ و IPv6 به ip -6 rule خودش نیاز دارد، چون قاعده v4 آن را پوشش نمی‌دهد۔

Windows و macOS: دیوار آتش را محدود کنید یا واسطه بسپارید

Windows هم گزینشگر مسیر به‌ازای برنامه ندارد، اما قواعد دیوار آتش آن را می‌توان به یک پروفایل محدود کرد، و می‌توان کارت VPN را «عمومی» طبقه‌بندی کرد در حالی که LAN خانه «خصوصی» می‌ماند۔ برای برنامه در پروفایل‌های «خصوصی» و «دامنه» قاعده مسدودسازی خروجی بسازید، «عمومی» را باز بگذارید، و برنامه تنها از راه تونل به شبکه می‌رسد۔ نخست دسته شبکه اتصال را تعیین کنید، سپس تأیید کنید که قاعده در پنجره پایش دیوار آتش تطبیق خورده است۔ این یک مسدودسازی است، نه مسیر: هنگام افتادن تونل بسته شکست می‌خورد، و نمی‌تواند برنامه‌ای را که دیوار آتش را نادیده می‌گیرد متوقف کند۔

macOS با ابزارهای دیگر همان منطق را دنبال می‌کند۔ شنونده SOCKS که از راه SSH به آن می‌رسیم کم‌مداخله‌ترین گزینه است، چون ssh -D 1080 user@host یک درگاه محلی می‌سازد و تنها برنامه‌هایی که برای استفاده از آن پیکربندی شده‌اند این راه را می‌روند۔ قاعده pf می‌تواند برنامه‌ای را بر پایه کاربر یا گروه مسدود کند، اما هنوز نمی‌تواند تونل را تنها برای همان برنامه برگزیند۔ Tunnelblick اصلاً نمی‌تواند بر پایه دامنه پالایش کند، چون مسیرها با نشانی‌ها کار می‌کنند؛ کاربران OpenConnect وقتی کارساز فهرست پیشوند خیلی پهنی را هل می‌دهد به vpn-slice پناه می‌برند۔

دو راه چاره آنچه ابزارهای سکو از دست می‌دهند را پوشش می‌دهد۔ افزونه‌های مرورگر که واسطه را بر پایه دامنه عوض می‌کنند پاسخ عملی «فقط این سایت» هستند، چون در لایه برنامه عمل می‌کنند جایی که نام‌ها هنوز وجود دارند؛ یک مرورگر را پوشش می‌دهند و هیچ چیز دیگر را۔ راه خام اجرای کارخواه فروشنده در یک ماشین مجازی است تا مهمان مالک تغییر مسیر باشد و میزبان مسیر خود را نگه دارد، به بهای دو پشته شبکه برای نگهداری۔

یک پرش SOCKS برای ابزارهایی که گزینه مقیدسازی ندارند

بیشتر برنامه‌ها را می‌توان به یک واسطه HTTP یا SOCKS نشانه گرفت حتی وقتی هیچ چیزی از واسط‌ها نمی‌دانند۔ یک کارخواه SSH یکی را بدون هیچ تغییری در سمت کارساز فراهم می‌کند:

ssh -N -D 127.0.0.1:9999 [email protected]

هر چیزی که برای استفاده از آن درگاه پیکربندی شده باشد از باستیون بیرون می‌رود در حالی که بقیه میزکار اتصال عادی خود را نگه می‌دارد۔ یک نمونه محلی 3proxy مقیدشده به نشانی تونل همین کار را برای یک درگاه بدون SSH انجام می‌دهد، و stunnel یک درگاه پیچیده‌شده در TLS را پوشش می‌دهد۔ محدودیت‌ها عملی‌اند: واسطه تنها پروتکل‌هایی را می‌برد که می‌دانند چگونه از آن استفاده کنند، UDP به انجمن UDP در SOCKS5 نیاز دارد که تنظیم «SOCKS5» بسیاری از کارخواه‌ها آن را فعال نمی‌کند، و DNS باید توسط واسطه حل شود (socks5h در curl)، وگرنه نام به حل‌کننده محلی نشت می‌کند۔

شنونده را روی حلقه بازگشت نگه دارید مگر اینکه ماشین دیگری به آن نیاز داشته باشد۔

مسیر را با یک ضبط بررسی کنید، نه با پیام وضعیت

پنجره‌های وضعیت برنامه تنظیم را توصیف می‌کنند، نه بسته را۔ روی هر دو واسط ضبط کنید و مقایسه کنید:

tcpdump -i any -n port 443
ss -tnp | grep 443

اگر اتصال روی واسط فیزیکی ظاهر شود بدون ورودی متناظر روی تونل، گزینشگر در کار نیست، هر چه کارخواه گزارش دهد۔ IPv6 و حل‌کننده را جداگانه بررسی کنید، و بررسی کنید مسیر دومی مثل یک فرایند کمکی یا واسطه سیستم به‌ارث‌رسیده وجود دارد یا نه۔

سپس مرزی را برگزینید که با نیاز می‌خواند: واسطه برای یک سایت کافی است، قاعده UID برای یک حساب کافی است، پروفایل دیوار آتش وقتی اتصال باید بسته شکست بخورد کافی است، و فضای نام تنها گزینه‌ای است که تونل را از همه چیز بیرونش پنهان می‌کند۔

مقالات مرتبط