মানটি VpnService.Builder.addDnsServer-এ একটি স্ট্রিং হিসেবে পৌঁছেছিল, এবং builder কিছুই প্রতিষ্ঠার আগেই সেখানেই তা প্রত্যাখ্যান করেছে। এই প্রত্যাখ্যান স্থানীয়: কোনো resolver-এ যোগাযোগ হয়নি, কোনো tunnel নেই, এবং ব্যর্থটি DNS বিভ্রাট নয়, ফরম্যাট যাচাই।

এটি গুরুত্বপূর্ণ কারণ সাধারণ VPN অ্যাপে একই রকম দেখতে তিনটি DNS ইনপুট থাকে: প্রোফাইলে টাইপ বা ইমপোর্ট করা মান, কনফিগারেশনের সময় সার্ভার যে মান পাঠায়, এবং সিস্টেম স্তরে সেট করা Private DNS। শুধু প্রথম দুটি এই কলে পৌঁছাতে পারে।

স্টেজিং অবজেক্ট আর্গুমেন্টটি সঙ্গে সঙ্গে যাচাই করে

VpnService.Builder tunnel নয়। এটি ঠিকানা, রুট ও সার্ভার সংগ্রহ করা একটি স্টেজিং অবজেক্ট, এবং কেবল establish() সেই তালিকাগুলোকে জীবন্ত ParcelFileDescriptor ও ইন্টারফেসে রূপান্তর করে। DNS এন্ট্রি যাচাই হয় addDnsServer কল করার সময়, প্রতিষ্ঠার সময় নয়।

দুটি পরিণতি। স্ট্যাক ট্রেস সংক্ষিপ্ত ও ইচ্ছাকৃতভাবে অনির্দেশক: মানটি সাধারণ স্ট্রিং হিসেবে এসেছে, তাই QR ইমপোর্ট না সার্ভার পেলোড তা ফ্রেমওয়ার্ক জানে না। আর পুরো বিল্ডের চারপাশে অসতর্ক catch ঠিকানা ও রুট আছে কিন্তু resolver নেই এমন tunnel রেখে যায়।

builder আপনার দেওয়া যেকোনো তালিকা রাখে। যাচাই পাস করা সার্ভার যোগ করা কেবল প্রমাণ করে যে স্ট্রিংটি বিল্ডের সময় ঠিকানা লিটারেল হিসেবে বিশ্লেষিত হয়েছে।

আর্গুমেন্টটি কী হতে পারে

প্যারামিটারটি ঠিকানা, endpoint নয়। Builder রেফারেন্স IPv4 বা IPv6 লিটারেল নেয়, আর বিশ্লেষণ কেবল সাংখ্যিক: android.net.InetAddresses.parseNumericAddress API 29 থেকে ওই পার্সারের প্রকাশ্য মুখ। হোস্টনেম এখানে ধীর ঠিকানা নয়, অবিশ্লেষণযোগ্য স্ট্রিং।

বাস্তব ম্যাপিং এমন দেখায়:

InputResultReason
10.8.0.1acceptedIPv4 literal
2606:4700:4700::1111acceptedIPv6 literal
dns.example.comrejectedhostname; numeric parsing does not resolve it
10.8.0.1:53rejectedport belongs to your own config model
https://dns.example.com/dns-queryrejectedDoH URL, a different configuration path
127.0.0.1rejectedloopback is not a remote resolver
0.0.0.0 or ::accepted by the builderparses fine, but names no destination; only your own validator rejects it

সংস্করণ প্রভাব ফেলে আপনি কীভাবে যাচাই করেন তাতে, builder কী নেয় তাতে নয়। InetAddresses API 29 থেকে পাওয়া যায়, তাই Android 10 ও পরবর্তীতে লিটারেল বিশ্লেষণ এক কল। String নতুন সংযোজন, addDnsServer(InetAddress) মূল রূপ। API 29-এর নিচে InetAddress.getByName ব্লকিং নেটওয়ার্ক লুকআপে হোস্টনেম সমাধান করে, ফলে টাইপো ধীর স্টার্টে পরিণত হয়।

লুপব্যাক এখানে প্রত্যাখ্যাত, wildcard নয়

ডেস্কটপ VPN ক্লায়েন্টরা প্রায়ই নিজেদের stub resolver 127.0.0.1:53-এ পাঠায়। এটি Android প্রোফাইলে অনুলিপি করলে স্থানীয় মেশিন বর্ণনা করা মান ঢোকে, কিন্তু এখানে স্থানীয় মেশিন নেই। builder কেবল এই ক্ষেত্রেই প্রত্যাখ্যান করে: null বা loopback ঠিকানা IllegalArgumentException নিক্ষেপ করে।

wildcard বিপরীত ক্ষেত্র, আর এখানেই এই ত্রুটির বেশিরভাগ পাঠ ভুল হয়। 0.0.0.0 ও :: loopback নয়, তাই builder এগুলো গ্রহণ করে; বিল্ডের সময় কিছুই নিক্ষিপ্ত হয় না। এরপর ইন্টারফেস কোনো গন্তব্য ছাড়া এন্ট্রি নিয়ে ওঠে। এটি ধরার কাজ আপনার ভ্যালিডেটরের: isAnyLocalAddress-কে অবৈধ ইনপুট ধরুন।

কনফিগারেশনের সীমানায় একবার বিশ্লেষণ করুন

সস্তা সমাধান হলো স্ট্রিংটি আপনার মডেলে ঢোকার জায়গাতেই যাচাই করা:

import android.net.InetAddresses
import java.net.InetAddress

private fun dnsLiteralOrNull(raw: String?): InetAddress? {
    val text = raw?.trim().orEmpty()
    if (text.isEmpty()) return null
    val address = try {
        InetAddresses.parseNumericAddress(text)
    } catch (e: IllegalArgumentException) {
        return null
    }
    return address.takeUnless { it.isAnyLocalAddress || it.isLoopbackAddress }
}

null ফলাফল দোষী টেক্সট বহনকারী ফিল্ড-স্তরের ত্রুটি হয়, আর সমর্থন আলোচনা কেবল এই রূপেই কাজ করতে পারে। সফল পথে অবজেক্টটি সরাসরি builder.addDnsServer(address)-এ যায়। IllegalArgumentException নির্দিষ্টভাবে ধরুন, Exception নয়: প্রশস্ত ধরা null পয়েন্টার বাগও গিলে ফেলে।

উদ্দিষ্ট মান প্রত্যাখ্যাত হলে পাবলিক resolver দিয়ে প্রতিস্থাপন করবেন না। নীরবে পাবলিক DNS বদলে দেওয়া প্রোফাইল সংযোগ করে, কিন্তু অভ্যন্তরীণ নাম সমাধান বন্ধ হয়ে যায়।

ইন্টারফেস তৈরির পর সংযোগযোগ্যতা নিশ্চিত করুন

builder-এর গ্রহণ প্রথম চেকপয়েন্ট, শেষ নয়। ইন্টারফেস ওঠার পর LinkProperties.getDnsServers() দেখায় নেটওয়ার্ক আসলে কী পেয়েছে, আর সেখানেই অমিল ধরা পড়ে। adb shell dumpsys connectivity বাইরে থেকে একই বৈশিষ্ট্য ছাপে।

Private DNS আলাদা নিয়ন্ত্রণ এবং এই আর্গুমেন্ট দিয়ে যায় না। DoT প্রোভাইডারের হোস্টনেম সিস্টেম সেটিংয়ের, IP ফিল্ডে পেস্ট করার মান নয়।

প্রোফাইল পার্সার আবার বদলানোর আগে এই ক্রমে কাজ করুন: কাঁচা স্ট্রিং ও তার উৎস লগ করুন, এটি হোস্টনেম নয় লিটারেল তা নিশ্চিত করুন, loopback বা wildcard নয় তা নিশ্চিত করুন, তারপর বিশ্লেষিত অবজেক্ট পাঠান।

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