এই বসন্তে একটি সকালের অধিকাংশ সময় আমি এমন একটি কন্টেইনার খুঁজতে কাটিয়েছি যা একটিও অভ্যন্তরীণ নাম রিজলভ করতে পারছিল না, অথচ পাশের ব্রাউজারটি কোনো অভিযোগ ছাড়াই উইকি খুলছিল। টানেল চালু ছিল, ব্রাউজার তা প্রমাণ করছিল, আর কন্টেইনারটি ছিল ভিন্ন রুট টেবিল ও ভিন্ন রিজলভারসহ ভিন্ন নেটওয়ার্ক নেমস্পেসে. এরপর যা আসে তা হলো এখন আমি যে ক্রমে যাচাই করি: প্রথমে রুট টেবিল, দ্বিতীয়ত রিজলিউশন, তৃতীয়ত ফরোয়ার্ডিং, আর সবার শেষে হোস্ট প্ল্যাটফর্ম। যে ধাপটি ইতিমধ্যে ব্যর্থ হয় সেখানেই থামুন, কারণ স্ট্যাকের নিচে নিয়ম যোগ করলে সীমানা সরানোর বদলে লুকিয়ে যায়।

কোনো নিয়ম পরিবর্তনের আগে তিনটি রুট টেবিল তুলনা করুন

Docker-এর ডিফল্ট ব্রিজে থাকা কন্টেইনারের docker0 গেটওয়ের দিকে নির্দেশ করা একটি ডিফল্ট রুট থাকে, এবং tun0 যে বিদ্যমান তা তার কোনো ধারণা নেই। হোস্টে tun0 আছে, VPN যে রুটগুলো ঠেলে দিয়েছে তা আছে, এবং হতে পারে এমন একটি পলিসি নিয়ম আছে যা প্রোব ট্রাফিক আপনার প্রত্যাশার চেয়ে ভিন্ন ইন্টারফেস দিয়ে পাঠায়। WSL নিজের ভার্চুয়াল অ্যাডাপ্টারের পেছনে তৃতীয় টেবিল যোগ করে। এগুলোর যেকোনোটি সম্পাদনার আগে তিনটিই প্রিন্ট করুন।

ContextCommandWhat a wrong answer looks like
Hostip route get 10.8.0.20Leaves a physical interface instead of tun0
Containerip routeOnly 172.17.0.0/16 via 172.17.0.1
Container DNScat /etc/resolv.confA public resolver, or an upstream the tunnel never pushed
WSLip addr; ip routeOnly the WSL subnet, no Windows VPN adapter

যদি কন্টেইনারের একমাত্র রুট ব্রিজ হয় এবং tun0 তার নেমস্পেসে কখনো না দেখা যায়, তবে নিচের কিছুই সাহায্য করবে না: কোনো নেমসার্ভার পরিবর্তন রুট তৈরি করে না, আর কোনো রুট এমন টানেল পরিচয় তৈরি করে না যা পিয়ার গ্রহণ করবে। ক্রম গুরুত্বপূর্ণ, কারণ DNS ব্যর্থতা ও রুট ব্যর্থতা শেলের ভেতর থেকে একই রকম দেখায়।

পাবলিক রিজলভারের বদলে রিজলভারকে টানেলের দিকে নির্দেশ করুন

হোস্টে, resolvectl status প্রতি লিঙ্কে DNS সার্ভার তালিকাভুক্ত করে, এবং readlink -f /etc/resolv.conf জানায় ফাইলটি systemd স্টাব, NetworkManager ফাইল, নাকি কোনো ভেন্ডর ক্লায়েন্টের পুনর্লিখিত কিছু। ব্যবহারকারী-নির্ধারিত নেটওয়ার্কে থাকা কন্টেইনারের ভেতরে, /etc/resolv.conf নাম দেয় 127.0.0.11, Docker-এর এমবেডেড রিজলভার, যা ডেমন যে আপস্ট্রিম দিয়ে কনফিগার হয়েছিল সেখানে ফরোয়ার্ড করে। ডিফল্ট ব্রিজের কন্টেইনার বরং তৈরি হওয়ার সময় হোস্ট ফাইলের একটি কপি পেয়েছিল এবং তা রাখে।

সেই কপির কারণেই কন্টেইনার চালু হওয়ার পরে ওঠা টানেল কন্টেইনারকে পুরোনো আপস্ট্রিমে কোয়েরি করতে ছেড়ে দেয়। হয় VPN রিজলভার স্পষ্টভাবে পাস করুন docker run --dns 10.8.0.53, অথবা "dns": ["10.8.0.53"] -এ /etc/docker/daemon.json সেট করে ডেমন পুনরায় চালু করুন, অথবা টানেল ওঠার পরে কন্টেইনারটি পুনরায় তৈরি করুন। পুনরায় তৈরি করা সাধারণত এক ঘণ্টা আগে সঠিক ছিল এমন রিজলভার ডিবাগ করার চেয়ে সস্তা।

পাবলিক রিজলভার বসানো হলো সেই মেরামত যা উপসর্গ ধরে রাখে। এটি ইন্টারনেট রিজলভ করে এবং প্রতিটি প্রাইভেট জোনে ব্যর্থ হয়, এবং টানেল যে ফিল্টারিং প্রয়োগ করতে বিদ্যমান তা চুপচাপ এড়িয়ে যেতে পারে। যদি আপনি চলমান কন্টেইনারের /etc/resolv.conf-এর ভেতরে লিখতে চেষ্টা করেন, প্রত্যাশা করুন Device or resource busy: Docker ওই ফাইলটি bind-mount করে, তাই সম্পাদনার আগে আনমাউন্ট দরকার। --dns তৈরির সময়েই সেট করা সেই ক্রম যা রিস্টার্টে টিকে থাকে।

কন্টেইনাররা টানেল ধার করলে ফরোয়ার্ড ও ট্রান্সলেট করুন

ডিফল্ট ব্রিজের সাথে, হোস্ট হলো docker0 ও tun0-এর মধ্যে একটি রাউটার, এবং ডিফল্টভাবে এটি দুটির কোনোটিই করে না। ফরোয়ার্ডিং চালু করুন এবং সোর্স অ্যাড্রেস ট্রান্সলেট করুন যাতে পিয়ার হোস্ট টানেল অ্যাড্রেস দেখে, এমন অ্যাড্রেস নয় যাতে ফেরার রুট নেই (tun0-কে আপনার ক্লায়েন্ট ইন্টারফেসের যে নাম দেয় তা দিয়ে প্রতিস্থাপন করুন):

sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
iptables -A FORWARD -i docker0 -o tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -o docker0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

FORWARD নিয়মগুলো সেই হোস্টে গুরুত্বপূর্ণ যাদের ফরোয়ার্ড পলিসি DROP। Docker নিজের চেইন যোগ করে, তাই পড়ার বদলে প্যাকেট দিয়ে পরীক্ষা করুন iptables -S: কন্টেইনার থেকে, ip route get 10.8.0.20 ব্রিজ গেটওয়ে নাম করা উচিত, এবং nc -vz 10.8.0.20 443 রিজলভারে নয়, টানেলে সংযুক্ত হওয়া বা টাইম আউট হওয়া উচিত।

MASQUERADE ছাড়া পিয়ারের 172.17.0.0/16-এ ফেরার রুট দরকার, যা অধিকাংশ রিমোট-অ্যাক্সেস VPN ঠেলে দেবে না। এখানেই পলিসি-ভিত্তিক ক্লায়েন্ট পরিকল্পনা ভাঙে: যদি এটি কেবল নির্দিষ্ট UID বা cgroups-এর ট্রাফিক ধরে, আপনার ফরোয়ার্ড নিয়ম টানেলের ভেতরে প্যাকেট কখনো দেখে না, এবং সমাধান iptables-এ নয়, ক্লায়েন্টের রাউটিং পলিসিতে। হাতে যোগ করা নিয়ম রিবুটে মুছে যায়, তাই সেগুলো systemd ইউনিট বা netfilter-persistent টেবিলে রাখুন এবং রিবুটের পরে আবার পরীক্ষা করুন, কারণ দেরিতে ওঠা টানেল সেগুলো ফ্লাশ করতে পারে।

লক্ষ্য বিচ্ছিন্নতা হলে টানেল কন্টেইনারের ভেতরে রাখুন

যদি একটি কন্টেইনারকে নির্দিষ্ট নোড দিয়ে বের হতে হয়, ওই কন্টেইনারে ক্লায়েন্ট চালান. এটির দরকার --cap-add NET_ADMIN এবং --device /dev/net/tun, আর তখন এটি নিজের tun0, রুট ও ফায়ারওয়ালের মালিক। যদি অন্য কন্টেইনার এর মধ্য দিয়ে যায়, এর ভেতরে ফরোয়ার্ডিং ও MASQUERADE নিয়মও দরকার, এবং সেই পিয়ারদের এর দিকে নির্দেশ করা স্পষ্ট রুট বা প্রক্সি দরকার; Docker-এর ডিফল্ট ব্রিজ আপনার জন্য সেই রুট যোগ করে না।

হোস্ট নেটওয়ার্কিং, docker run --network host, হলো সস্তা সংস্করণ: কন্টেইনার হোস্ট নেমস্পেসে যোগ দেয় এবং টানেল রুট সরাসরি দেখে। এটি নেটওয়ার্ক বিচ্ছিন্নতাও ফেলে দেয়, পোর্ট সংঘর্ষ ডাকে, এবং বাস্তবে এটি Linux-এর একটি অপশন। প্রকাশিত পোর্ট এর বিকল্প নয়। -p 8080:80 একটি সার্ভিসকে হোস্ট থেকে পৌঁছানোযোগ্য করে; এটি অভ্যন্তরীণ 10.x অ্যাড্রেসগুলোকে কন্টেইনারের ভেতর থেকে পৌঁছানোযোগ্য করে না, এবং দুটিকে গুলিয়ে ফেলা মানেই রাউটিং সমস্যাকে উন্মুক্ত ডেটাবেস পোর্টে পরিণত করা।

নাম ব্যর্থতাকে ওভারল্যাপিং সাবনেট থেকে আলাদা করুন

Docker-এর ডিফল্ট ব্রিজ 172.17.0.0/16, এবং অনেক কর্পোরেট VPN পুল 172.16.0.0/12-এর ভেতরে থাকে। একবার টানেল ওই রেঞ্জের মালিক হলে, কন্টেইনারের জন্য নির্দিষ্ট ট্রাফিক তার বদলে টানেলে রাউট হতে পারে। নাম ব্যর্থ হয়, ত্রুটি DNS-এর মতো দেখায়, আর সমস্যা অ্যাড্রেসগুলোতে। গন্তব্য যাচাই করুন, রিজলভার নয়: ip route get 172.17.0.5 হোস্টে, তারপর ব্রিজ প্রিফিক্সকে VPN পুল ও docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'-এর সাথে তুলনা করুন।

সমাধান হলো রেঞ্জগুলোর একটি সরানো—হয় Docker-এর জন্য ভিন্ন ডিফল্ট অ্যাড্রেস পুল daemon.json ফাইলে, নয়তো কনসেন্ট্রেটরে ভিন্ন পুল। এখানে আরেকটি নেমসার্ভার যোগ করলে কিছুই হয় না, এবং ssh -b ব্যবহার করলেও কিছু হয় না: ক্লায়েন্টকে ইন্টারফেসে বাঁধলে সোর্স অ্যাড্রেস বদলায়, টানেলে নির্দেশ করা গন্তব্য রুট নয়। দ্রুত কাজের ভাগ: হোস্টে রিজলভ হয় কিন্তু কন্টেইনারে হয় না এমন নাম রিজলভার সমস্যা, আর কন্টেইনারে আটকে থাকে কিন্তু হোস্টে কাজ করে এমন সরাসরি IP সংযোগ রুট বা ফরোয়ার্ড-চেইন সমস্যা।

WSL ও Docker Desktop Linux-এর নিয়ম মানে না

ডিফল্ট NAT মোডে WSL নিজের ভার্চুয়াল অ্যাডাপ্টারের পেছনে থাকে এবং Windows VPN-এর অভ্যন্তরীণ রুট বা DNS সাফিক্স পায় না, তাই ব্রাউজার কাজ করে আর ping ডিস্ট্রিবিউশনের ভেতরে কাজ করে না। সাম্প্রতিক Windows 11 বিল্ডে উপলব্ধ মিররড নেটওয়ার্কিং বরং হোস্ট ইন্টারফেস শেয়ার করে, কিন্তু এটি .wslconfig -এ সক্রিয় করতে হবে এবং ডিস্ট্রিবিউশন wsl --shutdown দিয়ে রিস্টার্ট করতে হবে। সক্রিয় মোড নিশ্চিত করুন wslinfo --networking-mode দিয়ে, ঠিক করার আগে, কারণ NAT-এর উত্তর মিররড মোডের জন্য ভুল এবং উল্টোটাও।

Docker Desktop আরেকটি স্তর যোগ করে। Windows-এ Linux কন্টেইনারগুলো ঐতিহাসিকভাবে vpnkit নামে পরিচিত একটি NAT বাস্তবায়নের পেছনে থাকে, এবং তাদের পথ নেটিভ Linux হোস্ট থেকে আলাদা। Windows কন্টেইনারগুলো Hyper-V ভার্চুয়াল সুইচের মাধ্যমে সংযুক্ত হয়, এবং কর্পোরেট ক্লায়েন্ট সেই সুইচ থেকে ট্রাফিক ব্লক করতে পারে, তাই একই মেশিনের Linux কন্টেইনার নেটওয়ার্কে পৌঁছাতে পারে অথচ Windows কন্টেইনারের কিছুই নেই। ফিজিক্যাল অ্যাডাপ্টারে বাঁধা একটি এক্সটার্নাল সুইচ হলো স্বাভাবিক সমাধান, এবং এটি কন্টেইনারের নেটওয়ার্ক পরিচয় এমনভাবে বদলায় যা পলিসি অনুমোদন নাও করতে পারে।

আরেকটি নিয়ম লেখার আগে চারটি স্তর ক্রমে হাঁটুন এবং যেখানে একটির ভুল সেখানেই থামুন:

  • কন্টেইনারের ভেতরে: ip route কি প্রাইভেট প্রিফিক্সে পৌঁছায়, এবং /etc/resolv.conf কি এমন আপস্ট্রিমের নাম দেয় যা এটি রিজলভ করে?
  • হোস্টে: ফরোয়ার্ডিং চালু আছে কি, NAT টানেল ইন্টারফেসে বাঁধা কি, এবং ফরোয়ার্ড চেইন কি ব্রিজ থেকে টানেলে গ্রহণ করে?
  • পিয়ারে: এটি কি ফেরার পথ জানে, নাকি আপনি NAT-এর উপর নির্ভর করছেন কন্টেইনার সাবনেট লুকাতে?
  • প্ল্যাটফর্মে: WSL NAT না মিররড মোডে, এবং কন্টেইনার Linux না Windows?

প্রতিটি পরীক্ষা নতুন কন্টেইনার বা শেলে আবার চালান। ক্যাশ করা উত্তর বা প্রতিষ্ঠিত সংযোগসহ দীর্ঘকালীন প্রসেস কনফিগারেশন সঠিক হওয়ার পরেও ব্যর্থ হতে থাকবে, এবং সেই ব্যর্থতা ঠিক এমন একটি সমাধানের মতো দেখায় যা কাজ করেনি।

সম্পর্কিত নিবন্ধ