چگونه ترافیک میزبان را میتوان از کانتینر VPN داکر گذراند
کانتینر VPN یک فضای نام شبکه است، نه یک پروکسی. یک netns مشترک، شنونده واقعی SOCKS یا دروازه مسیریابیشده انتخاب کنید و سپس مسیر خروج را بررسی کنید.
کانتینر تونل شما کار میکند، docker compose exec vpn curl -s https://ifconfig.me با نشانی ارائهدهنده پاسخ میدهد، و با این حال مرورگر روی میز کار شما هنوز از طریق ISP شما خارج میشود. این شکاف قابل انتظار است. کانتینری که کلاینت VPN را اجرا میکند یک فضای نام شبکه، مسیرها و یک رابط تونل نگه میدارد، و هیچ چیزی که HTTP یا SOCKS صحبت کند؛ انتشار یک پورت روی آن نمیتواند شنوندهای بسازد که هرگز وجود نداشته است.
اینکه کلاینت کجا اجرا میشود طراحی را تعیین میکند. یک کانتینر همسایه میتواند به فضای نام تونل برود، یک برنامه میز کار نمیتواند، و یک روتر به مسیرها نیاز دارد نه به فیلد پروکسی. اگر نگاشت را اشتباه کنید، نشانه یکسان است: اتصال رد شده یا ترافیکی که بیصدا مسیر قدیمی را میگیرد.
چیدمان را با جایی که کلاینت اجرا میشود هماهنگ کنید
پیش از ویرایش Compose یک پرسش بپرسید: آیا کلاینت میتواند درون فضای نام شبکه کانتینر VPN آغاز شود؟ هر چیزی که از قبل بهعنوان کانتینر لینوکس اجرا میشود میتواند. یک مرورگر، یک IDE یا یک CLI روی میز شما نمیتواند، زیرا تنظیم پروکسی آن انتظار یک سرویس پروتکلی دارد، نه یک فضای نام.
| Client | Arrangement | What must already exist |
|---|---|---|
| Another Compose service | Shares the vpn namespace via network_mode: service:vpn | A working tunnel and correct start order |
| Host browser, IDE or CLI | Proxy listener inside the namespace, published to host loopback | A configured SOCKS or HTTP proxy and its access rules |
| Router, VM or phone | The container as a layer-3 gateway | Forwarding, NAT and a return route |
این سه با هم مخلوط نمیشوند. یک فیلد پروکسی HTTP که به نشانی پل کانتینر اشاره میکند به هیچ چیز نمیرسد مگر اینکه پروکسی آنجا گوش دهد، و یک شنونده پروکسی ترافیک روتر را مسیریابی نمیکند. نخست تصمیم بگیرید، سپس بخش مربوط را دنبال کنید.
فضای نام تونل را با یک کانتینر همسایه به اشتراک بگذارید
این ارزانترین گزینه است زیرا هیچ پروتکل پروکسی درگیر نیست. برنامه به فضای نام سرویس vpn میپیوندد و رابطها، مسیرها و فضای پورت آن را به ارث میبرد:
services:
vpn:
image: your-vpn-client
cap_add:
- NET_ADMIN
healthcheck:
test: ["CMD-SHELL", "ip link show tun0 >/dev/null"]
interval: 10s
retries: 5
app:
image: your-app
network_mode: service:vpn
depends_on:
vpn:
condition: service_healthyسه محدودیت از این واقعیت ناشی میشود که فضای نام به سرویس دیگری تعلق دارد. برنامه کلید networks خودش را نمیگیرد، زیرا فضای نامی برای چسبیدن ندارد. انتشار پورتها بر عهده vpn است، چون فضای نام همانجا زندگی میکند. و بازآفرینی vpn فضای نامی را که برنامه به آن پیوسته بود نابود میکند، بنابراین docker compose up -d --force-recreate vpn شما را وا میدارد که app را هم بازآفرینی کنید؛ یک راهاندازی مجدد ساده همیشه کافی نیست. مرجع سرویسهای Compose این چیدمان و محدودیت انتشار پورت آن را پوشش میدهد.
فرض نکنید که حلکننده همراه میآید. /etc/resolv.conf یک فایل برای هر کانتینر است نه بخشی از فضای نام شبکه، بنابراین برنامه هنوز میتواند از حلکننده میزبان بپرسد در حالی که بستههایش از تونل خارج میشوند. آن را از درون کانتینر بخوانید و وقتی نامهای خصوصی باید از تونل حل شوند dns: را روی برنامه تنظیم کنید.
docker compose exec app ip route
docker compose exec app cat /etc/resolv.conf
docker compose exec app curl -s https://ifconfig.meاگر فهرست مسیرها رابط تونل را دارد و نشانی از آنِ ارائهدهنده است، برنامه درون تونل است. اگر رابط نیست، app پیش از آنکه vpn تونلی داشته باشد آغاز شده، پس پس از گزارش سلامت vpn، app را دوباره بسازید.
یک شنونده پروکسی واقعی در فضای نام برای کلاینتهای میزبان بگذارید
مرورگر میز کار نمیتواند به فضای نام بپیوندد، پس تونل باید بهعنوان سرویس آشکار شود. یک پروکسی واقعی SOCKS یا HTTP درون فضای نام vpn اجرا کنید و فقط پورت آن را منتشر کنید:
services:
vpn:
ports:
- "127.0.0.1:1080:1080"
proxy:
image: your-proxy
network_mode: service:vpn
command: ["--socks", "0.0.0.0:1080"]نشانی سمت میزبان نیمه مهم آن نگاشت است. 127.0.0.1:1080 در سمت چپ شنونده را بیرون از LAN شما نگه میدارد؛ "1080:1080" لخت یک پروکسی باز را برای هر دستگاهی که به ماشین شما برسد منتشر میکند.
سمت کانتینر تله خودش را دارد. پورتهای منتشرشده به نشانی کانتینر روی پل تحویل داده میشوند، نه به loopback آن، بنابراین پروکسیای که با --socks 127.0.0.1:1080 درون فضای نام آغاز شده اتصالهای همسایهها را میپذیرد و هر چیزی را که از پورت منتشرشده برسد رد میکند. آن را به 0.0.0.0 ببندید و برای جداسازی به بستن loopback در سمت میزبان تکیه کنید. همین قاعده برای نشانی شنیدن در فایل پیکربندی پروکسی هم صدق میکند.
سپس تصمیم بگیرید نامها کجا حل شوند. در curl، --socks5 نام میزبان را محلی حل میکند و --socks5-hostname نام را به پروکسی میفرستد؛ مرورگرها همان کلید را زیر برچسبی دیگر نشان میدهند. حل محلی حتی وقتی صفحه از تونل بارگذاری میشود پرسوجو را لو میدهد.
کانتینر را فقط با مسیر بازگشت بهعنوان دروازه در نظر بگیرید
روترها، ماشینهای مجازی و تلفنها از فیلد پروکسی HTTP استفاده نمیکنند و برخی برنامهها متغیرهای پروکسی را کاملاً نادیده میگیرند. مسیریابی ترافیک آنها یعنی لایه 3: کانتینر بستهها را جلو میفرستد و نشانیها را روی رابط تونل ترجمه میکند.
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADEدو چیز آسان فراموش میشود. کلاینت به مسیری نیاز دارد که دروازه پیشفرضش را به این میزبان ببرد، و پاسخها باید راه بازگشت را بیابند، یا از طریق یک مسیر ایستا روی روتر بالادست یا از طریق NAT روی رابط رو به کلاینتها. بدون یکی از این دو، تونل درخواست را بیرون میبرد و پاسخ ناپدید میشود.
همچنین گستردهترین شعاع اثر را دارد. میزبانی که بهعنوان دروازه برای تمام LAN عمل میکند هر کلاینت بدتنظیم را از حساب VPN شما میفرستد، و افتادن تونل یا LAN را تاریک میکند یا بسته به kill switch به ISP بازمیگردد.
مسیر خروج را بررسی کنید، سپس به ترتیب عقبگرد کنید
به این ترتیب بررسی کنید و در نخستین شکست بایستید: نشانی خروج درون فضای نام، نشانی خروج از میزبان از طریق پروکسی، سپس حل نام برای یک میزبان خصوصی.
docker compose exec vpn curl -s https://ifconfig.me
curl -s -x socks5h://127.0.0.1:1080 https://ifconfig.me
curl -s -x socks5h://127.0.0.1:1080 http://internal.example/healthدو نشانی نخست باید با ارائهدهنده بخوانند و سومی باید از طریق پروکسی حل شود نه اینکه با خطای نام شکست بخورد. همخوانی خط اول و شکست خط دوم یعنی شنونده یا پورت منتشرشده اشتباه است، نه تونل.
عقبگرد کردن وارونه راهاندازی است. متغیرهای محیطی پروکسی را از پروفایل پوسته و از هر واحد systemd حذف کنید، سپس پشته را پایین بیاورید تا فضای نام مشترک با مالکش ناپدید شود. پورت منتشرشده را پیش از حذف سرویس پروکسی پاک کنید، وگرنه میزبان یک شنونده loopback نگه میدارد که هیچکس آن را نمیخواند.
پیش از ادامه چهار چیز را تأیید کنید: کلاینتی که برایتان مهم است واقعاً به تونل میرسد، تنها مالک فضای نام پورتها را منتشر میکند، نامهای خصوصی همانجا که قصد داشتید حل میشوند، و هیچ چیز روی نشانی میزبانی که loopback نیست گوش نمیدهد. هر یک از اینها در تولید بیصدا شکست میخورد و هر یک بررسی یکخطی بالاست.