কন্টেইনারের প্যাকেট কখনো tun0 এ পৌঁছায় না, আর কন্টেইনারে অতিরিক্ত ক্যাপাবিলিটি যোগ করলে তা বদলাবে না। অতিথি ও টানেলের মাঝে দুটি রুট লুকআপ আছে: একটি অতিথির নেটওয়ার্ক নেমস্পেসের ভিতরে, আরেকটি হোস্টে ফ্রেম ব্রিজে পড়ার পর, এবং দ্বিতীয়টি ভিন্ন ম্যাচ নিয়মের ভিন্ন টেবিল দেখে। আমার রেফারেন্স বিন্যাস হলো একটি Linux হোস্ট, যেখানে OpenVPN ক্লায়েন্ট 10.8.0.10 ঠিকানাটি tun0 এ ধরে রাখে, 172.18.0.0/16 এ একটি Docker ব্রিজ, এবং একটি প্রাইভেট ঠিকানা যা কেবল টানেলের দিকেই পৌঁছাতে পারে। লক্ষ্য সংকীর্ণই থাকে: ওই অতিথি সাবনেট টানেল দিয়ে বেরোয়, আর হোস্ট তার স্বাভাবিক আপলিংক ধরে রাখে।

কন্টেইনারের ভিতরে VPN ক্লায়েন্ট চালানো ভিন্ন এক নকশা, কোনো শর্টকাট নয়। তখন কন্টেইনার নিজের টানেল ও ক্রেডেনশিয়ালের মালিক, আর কাজটা iptables-এ না থেকে Dockerfile-এ চলে যায়।

ফরওয়ার্ডিং গন্তব্য দেখে রুট বেছে নেয়, তারপর NAT সোর্স লিখে দেয়

যখন net.ipv4.ip_forward=1 সেট থাকে, ফরওয়ার্ড করা প্যাকেট কেবল গন্তব্য অনুযায়ী, মূল টেবিলে রাউট হয়। সেই সিদ্ধান্তে সোর্সের কোনো ভূমিকা নেই। রুট বেছে নেওয়ার পরেই কার্নেল POSTROUTING চালায়, আর ঠিক ওখানেই Docker-এর নিজের নিয়মটি বসে: -s 172.18.0.0/16 ! -o br-appnet -j MASQUERADE। ব্রিজ ছেড়ে অন্য ইন্টারফেসে যাওয়া সবকিছু অনূদিত হয়, আর সেটি যে ঠিকানা হয় তা নির্ভর করে রুট কোন ইন্টারফেসে পাঠিয়েছে তার উপর।

এই নিয়মটাই আমার কাছে যেসব লক্ষণ জানানো হয় তার বেশিরভাগ ব্যাখ্যা করে। মূল টেবিলে প্রাইভেট গন্তব্যের জন্য রুট না থাকলে প্যাকেট হোস্টের ভৌত ঠিকানা নিয়ে আপলিংক দিয়ে বেরিয়ে যায়। আপনার প্রোভাইডার গন্তব্যটি ফেলে দেয়, বা দূরের সাইট এমন একটি RFC 1918 সোর্স ফেলে দেয় যা সে কখনো আশা করেনি, আর টানেল চালু ও সুস্থ থাকলেও কন্টেইনারের সময় শেষ হয়ে যায়। tun0 এ ক্যাপচার কিছুই দেখায় না, আর এটাই সূত্র। রুট যদি সত্যিই tun0 এর দিকে থাকে, MASQUERADE সোর্সটিকে 10.8.0.10 এ লিখে দেয় এবং একই কন্টেইনার আপনি কিছু না ছুঁয়েই কাজ করে।

অতিথির সোর্স নামায় এমন লুকআপ চান। from ও iif ছাড়া হোস্ট নিজের সকেটের হয়ে উত্তর দেয় এবং এমন পথ জানায় যা ফরওয়ার্ড করা প্যাকেট কখনো নেবে না:

ip route get 192.168.11.222 from 172.18.0.5 iif br-appnet

সেই উত্তর যদি আপলিংকের নাম নেয়, টানেল ছবির বাইরে এবং টানেলের দিকের কোনো সেটিংসই সাহায্য করবে না।

অতিথিকে টানেলের নিজের ঠিকানায় নিয়ে যান

সবচেয়ে ছোট কাজ করা পরিবর্তন হলো বেরোনোর পথে স্পষ্ট সোর্স পুনর্লিখন। এটি যোগ করুন, শেষে জুড়বেন না: Docker ইতিমধ্যে POSTROUTING এর শুরুতেই বসে আছে, তাই -A দিয়ে যোগ করা নিয়ম Docker-এর নিয়মের পরে মূল্যায়িত হয় এবং কখনো চলে না।

iptables -t nat -I POSTROUTING 1 -s 172.18.0.0/16 -o tun0 -j SNAT --to-source 10.8.0.10

দুটি শর্ত ঠিক করে এটি টিকবে কি না। ঠিকানাটি অবশ্যই এমন হতে হবে যা ক্লায়েন্ট সত্যিই ধরে রাখে, তাই প্রতিটি ক্লায়েন্টের জন্য ifconfig-push 10.8.0.10 255.255.255.0 থাকা এন্ট্রি দিয়ে এটি পিন করুন; যে নিয়ম এমন ঠিকানার নাম নেয় যা কখনো বরাদ্দ হয়নি, সেটি নীরব ড্রপ ঘটায়, ত্রুটি নয়। আর দূরের প্রান্তকে সেই সোর্স গ্রহণ করতে হবে এবং উত্তর ফেরত রাউট করতে হবে, তাই ওখানকার অনুমোদিত তালিকা অতিথি সাবনেট নয়, টানেল ঠিকানার নাম নেয়। ক্লায়েন্ট ঠিকানা গতিশীল হলে, -j MASQUERADE সহ -o tun0 ইন্টারফেস যা ধরে রাখে তা-ই অনুসরণ করে, তবে এমন দূরবর্তী নীতির ব্যয়ে যার বিরুদ্ধে আপনি নির্দিষ্ট মান লিখতে পারবেন না।

ফিল্টারিং হলো অন্য অর্ধেক। Docker FORWARD নীতিকে drop-এ রাখে এবং প্রতিবার ডেমন রিস্টার্টে তার মালিকানার চেইনগুলো খালি করে, তাই স্থানীয় নীতি রাখুন DOCKER-USER এ, যা টিকে থাকে:

iptables -I DOCKER-USER -i br-appnet -o tun0 -j ACCEPT
iptables -I DOCKER-USER -i tun0 -o br-appnet -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

ফেরতের নিয়মটি গুরুত্বপূর্ণ: tun0 এ আসা উত্তর ব্রিজ দিয়ে খোলা সংযোগের জন্য কেবল বাইরের দিক গ্রহণ করা হয়েছিল বলে গ্রহণ করা হয় না।

সোর্স নিয়ম দিয়ে হোস্টকে সরাসরি রাখুন

যখন পুরো ব্রিজের টানেল ব্যবহার করা উচিত কিন্তু হোস্টের আর কিছুই নয়, NAT বাদ দিন এবং সেই সোর্সকে নিজের টেবিল দিন। অতিথির ঠিকানাগুলো তখন শুরু থেকে শেষ পর্যন্ত দৃশ্যমান থাকে।

echo "200 vpnbox" >> /etc/iproute2/rt_tables
ip rule add from 172.18.0.0/16 lookup vpnbox pref 1000
ip route add default dev tun0 table vpnbox

নিয়মগুলো মূল টেবিলের আগে দেখা হয়, এবং from সোর্সের সঙ্গে মেলে। টানেলের নিজের বাইরের প্যাকেট হোস্টের ভৌত সোর্স বহন করে, তাই সেগুলো কখনো from 172.18.0.0/16 এর সঙ্গে মেলে না এবং টানেল নিজেকে নিজের ভিতরে ফেরত রাউট করতে পারে না। এই আত্ম-বহিষ্কারই বিন্যাসটিকে স্থিতিশীল করে।

উত্তরের পথের এখন সাহায্য দরকার। দূরের প্রান্ত একটি অনূদিত 172.18.0.x দেখে এবং তার ঠিকানা কোথায় তা জানার কারণ নেই। সার্ভারে প্রতিটি ক্লায়েন্টের ফাইলে iroute 172.18.0.0 255.255.0.0 দরকার, যাতে সার্ভারের অভ্যন্তরীণ টেবিল সেই প্রিফিক্স এই ক্লায়েন্টকে দেয়, এবং পাঠানো route 172.18.0.0 255.255.0.0 দরকার, যাতে অন্য ক্লায়েন্টরা জানে এটি আছে।

বিশ্বাস করার আগে দুটি পরীক্ষা। চালান ip route show default: redirect-gateway def1 সহ টেবিলে 0.0.0.0/1 ও 128.0.0.0/1 আছে tun0 দিয়ে, এবং হোস্ট মোটেও সরাসরি নয়, কারণ ওই অর্ধ-ডিফল্টগুলো default প্রতিস্থাপন না করেই জিতে যায়। ip rule add দিয়ে যোগ করা নিয়মও রিবুটে হারিয়ে যায়, তাই সেগুলো একটি up স্ক্রিপ্টে রাখুন।

প্যাকেটের আকার হলো দ্বিতীয় ব্যর্থতা যা আপনি দেখবেন

1500 বাইটের একটি ব্রিজ ইন্টারফেস বাইরের UDP ও IP হেডার যোগ হলে টানেলকে তার সামর্থ্যের বেশি দেয়। ছোট বিনিময় পার হয়, তাই DNS সমাধান হয় ও TLS হ্যান্ডশেক শেষ হয়, তারপর বড় আপলোড কোথাও কোনো ত্রুটি ছাড়াই আটকে যায়। প্রেরক একই সেগমেন্ট আকার বারবার চেষ্টা করে, কারণ ICMP-র ফ্র্যাগমেন্টেশন-প্রয়োজন বার্তা, যা তাকে ছোট হতে বলত, কখনো পৌঁছায় না।

ইন্টারফেসে, MSS-এ, বা দুটোতেই ঠিক করুন। ক্ল্যাম্পিংয়ে অতিথির কোনো সহযোগিতা লাগে না:

ip link set dev tun0 mtu 1400
iptables -t mangle -A FORWARD -o tun0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

ক্ল্যাম্প শুধু TCP পুনর্লিখন করে। UDP, QUIC সহ, বরং অতিথির ইন্টারফেস কমাতে হয়, --opt com.docker.network.driver.mtu=1400 দিয়ে নেটওয়ার্ক তৈরির সময় বা মিলে যাওয়া compose কী।

DNS এবং যেসব যাচাই বারবার করার যোগ্য

নাম সমাধান একই পথ অনুসরণ করে। Docker-এর এমবেডেড রিজলভার 127.0.0.11 এ হোস্টের কনফিগার করা সার্ভারে ফরওয়ার্ড করে, তাই কন্টেইনার হোস্ট যা ব্যবহার করে তা উত্তরাধিকার পায়। প্রাইভেট জোন যদি কেবল টানেল দিয়ে পৌঁছানো রিজলভার উত্তর দেয়, অতিথিকে সেই ঠিকানায় নির্দেশ করুন, এবং getent hosts দিয়ে কন্টেইনারের ভিতরে পরীক্ষা করুন, হোস্টে নয়।

  • ব্যর্থ অনুরোধটি অতিথির ভিতরে চালান; হোস্টের দিকের পরীক্ষা ভিন্ন সোর্স ঠিকানা ব্যবহার করে সমস্যা ঢেকে দেয়।
  • আপনি যোগ করা NAT ও ফিল্টার নিয়মের কাউন্টার পড়ুন। শূন্য ম্যাচের নিয়ম রাউটিং ভুল, নীতি ভুল নয়।
  • আপনার SNAT নিয়মের টানেল ঠিকানাটি ক্লায়েন্ট সত্যিই ধরে রাখে কি না নিশ্চিত করুন।
  • আটকে যাওয়ার জন্য সার্ভারকে দোষ দেওয়ার আগে অতিথির MTU কমান বা MSS ক্ল্যাম্প করুন।

তালিকাটি ধরে এগোন এবং প্রথম ব্যর্থতায় থামুন। ওই স্তরটাই ঠিক করার; আর নিচে যা যোগ করা হয় তা কেবল ভাঙা সীমাটাই ঢাকে।

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