VPN-ის ამოცნობა Android-ზე: რატომ ატყუებენ tun0 და ping
ინტერფეისის სახელი კონვენციაა; ping ამტკიცებს, რომ ერთი ჰოსტი ხელმისაწვდომი იყო. ჰკითხეთ NetworkCapabilities-ს, რას აცხადებს ქსელი, შემდეგ უპასუხეთ callback-ებს.
შემოწმება, რომელსაც Android-ის აპების უმეტესობა VPN-ის ამოსაცნობად იყენებს, ინტერფეისის სახელია და ის არასოდეს ყოფილა VPN-ის შემოწმება. ის აცხადებს, რომ არსებობს რაღაც ინტერფეისი დეველოპერის მოსალოდნელი სტრიქონით, რაც ერთ მოწყობილობაზე OpenVPN-ს ნიშნავს, მეორეზე კი სრულიად შეუსაბამო რამეს. ping იგივე ტიპის მტკიცებულებაა: ის ამტკიცებს, რომ ერთმა დანიშნულებამ უპასუხა, და არა იმას, რომ გვირაბმა გადაიტანა პაკეტი. რასაც პლატფორმა რეალურად ამჟღავნებს, არის ქსელების ერთობლიობა და ტრანსპორტები, რომლებსაც თითოეული მათგანი აცხადებს, და ეს სხვა პასუხის მქონე სხვა კითხვაა.
მითი: თუ tun0 არსებობს, VPN ჩართულია
Android-ის VPN აპები VpnService-ის იმპლემენტაციებია, რომლებიც სისტემას tun ფაილის დესკრიპტორს გადასცემენ. ინტერფეისის სახელი მისი შემქმნელისგან მოდის: OpenVPN ჩვეულებრივ tun0-ს ქმნის, PPTP და L2TP ქმნიან ppp0-ს, WireGuard ინტერფეისს გვირაბის სახელს აძლევს, ხოლო IPsec გვირაბს შეიძლება საერთოდ არ ჰქონდეს ჩვეულებრივი სახელი. ამიტომ /proc/net/dev-ის კითხვა ან NetworkInterface ობიექტების ჩამოთვლა კონვენციას ეძებს და არა ფაქტს.
შეცდომის ორივე მიმართულება ცოცხალია. მოწყობილობას შეიძლება ჰქონდეს tun0 ლოკალური გვირაბისთვის, რომელსაც მომხმარებლის VPN-თან არაფერი აქვს საერთო, და სრულიად ჯანსაღი WireGuard გვირაბი შეიძლება უხილავი იყოს შემოწმებისთვის, რომელმაც მხოლოდ tun0 სტრიქონი იცის. ცრუ დადებითი და ცრუ უარყოფითი შედეგები ერთ ფუნქციაში მას დიაგნოსტიკად უვარგისს ხდის, რადგან „VPN ვერ აღმოჩნდა“ ტექსტის მქონე მხარდაჭერის ბილეთის არც ერთი მიმართულებით ნდობა არ შეიძლება.
წვდომაც შემცირდა. Android-ის ახალი ვერსიები ზღუდავს, თუ რისი წაკითხვა შეუძლია ჩვეულებრივ აპს /proc-იდან, ამიტომ სახელზე დაფუძნებულმა შემოწმებამ, რომელიც Android 8-ზე მუშაობდა, შეიძლება უფრო გვიანდელ მოწყობილობაზე ცარიელი სია დააბრუნოს კოდის ყოველგვარი ცვლილების გარეშე. როცა შემოწმება ჩუმად მარცხდება ღია მხარეს, აპი ასკვნის, რომ VPN არ არის იმ ტექნიკაზე, სადაც გვირაბი კარგად მუშაობს.
მითი: წარმატებული ping ნიშნავს, რომ გვირაბი ტრაფიკს გადააქვს
შიდა ჰოსტზე ping წარმატებულია ყოველთვის, როცა ამ ჰოსტისკენ მარშრუტი არსებობს. გაყოფილი გვირაბის დროს ჰოსტი შეიძლება Wi-Fi-ით იყოს ხელმისაწვდომი, მაშინ როცა გვირაბი ჩართულია და სრულიად სხვა რამეს აკეთებს. აპლიკაციისთვის განკუთვნილი VPN-ის დროს ping-ის შემსრულებელი აპი შეიძლება გამორიცხული იყოს გვირაბიდან, ამიტომ ICMP ექო მის გარეთ გადის და ბრუნდება, რაც საპირისპიროს ამტკიცებს იმისა, რასაც შემოწმება ვარაუდობს.
ICMP და TCP ასევე ცალ-ცალკე იფილტრება. გვირაბი, რომელიც ექოს მოთხოვნებს უშვებს, მაგრამ ყრის იმ TCP პორტს, რომელიც სერვისს სჭირდება, გაივლის ping-ის ტესტს და ჩავარდება აპში, და ეს მეორე მხარის ფაირვოლის წესია და არა მოწყობილობის თვისება. წარმატებული ping-ის ერთადერთი პატიოსანი წაკითხვა ისაა, რომ ამ დანიშნულებამ ამ პროტოკოლს ამ მომენტში უპასუხა.
რასაც პლატფორმა რეალურად აცხადებს
API 21-დან ConnectivityManager.getAllNetworks() აბრუნებს ყველა ქსელს, რომელიც სისტემამ იცის, და getNetworkCapabilities(network) აცხადებს, თუ რას აცხადებს ეს ქსელი. VPN ქსელი ატარებს TRANSPORT_VPN-ს. ყველა ქსელზე გადასვლა პასუხობს ერთ კითხვას: ხომ არ არის ამ მოწყობილობაზე ახლა რომელიმე VPN ჩართული?
მეორე, უფრო ვიწრო კითხვა სხვა გამოძახებას მოითხოვს. getActiveNetwork() აბრუნებს იმ ქსელს, რომელსაც აპის ტრაფიკი ახლა გამოიყენებდა, და მასზე იმავე შესაძლებლობის შემოწმება პასუხობს: გაივლის ჩემი ტრაფიკი VPN-ში? ორივე შეიძლება არ დაემთხვეს, და ეს შეუთანხმებლობა ინფორმაციულია. ჩართული, მაგრამ არა მიმდინარე VPN მიუთითებს აპლიკაციის მიხედვით მარშრუტიზაციაზე ან გაყოფილი გვირაბის კონფიგურაციაზე და არა დაკარგულ გვირაბზე.
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean viaVpn = caps != null
&& caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
დაიცავით ორივე გამოძახება. შესაძლებლობები შეიძლება იყოს null ქსელისთვის, რომელიც გაქრა ჩამოთვლასა და შეკითხვას შორის, და აქტიური ქსელი შეიძლება იყოს null გადაცემის დროს. NET_CAPABILITY_NOT_VPN სასარგებლოა საპირისპირო მიმართულებით, რომ დაადასტუროთ, რომ ქსელი ნამდვილად არ არის გვირაბი და არა უბრალოდ მას აკლია VPN ტრანსპორტის დროშა.
NetworkInfo იგივე მითი იყო API-ით
ConnectivityManager.TYPE_VPN NetworkInfo-სთან ერთად ამ შემოწმების მეორე თაობაა და ის მიზეზის გამო მოძველებულია. თანამედროვე ვერსიებში VPN-ისთვის დაბრუნებული ობიექტი შეიძლება იყოს null, მაშინ როცა გვირაბი დაკავშირებულია, ამიტომ null-ს, როგორც „VPN არ არის“, ისე მოპყრობილი კოდი ჩუმად ცრუ უარყოფითს აცხადებს იმ მოწყობილობებზე, სადაც პასუხი ყველაზე მნიშვნელოვანია.
თანმხლები გადაცემა ასევე არ არის უკეთესი. CONNECTIVITY_CHANGE-ს აქვს VPN-ის გადასვლების გამოტოვების დოკუმენტირებული ისტორია, მათ შორის იმ ბილდების ჩათვლით, სადაც ის მხოლოდ VPN-ის დაკავშირების ან გათიშვისას ირთვებოდა და არა ყოველ ქსელურ ცვლილებაზე. მასზე აგებული მსმენელი ქმნის მდგომარეობის ცვლადს, რომელიც პლატფორმის ხედვას შორდება მომდევნო სრულ გადაერთებამდე.
უპასუხეთ callback-ებს და არა გამოკითხვას
დარეგისტრირდით NetworkRequest, რომელიც მოითხოვს TRANSPORT_VPN-ს და ხსნის NET_CAPABILITY_NOT_VPN-ს, შემდეგ გამოიძახეთ registerNetworkCallback ამ მოთხოვნით. შემდეგ onAvailable და onLost გეტყვიან, როდის იცვლება დეკლარაცია, და ეს არის ის მოვლენა, რომელიც ნამდვილად გაინტერესებთ. გააუქმეთ callback-ის რეგისტრაცია, როცა კომპონენტი ჩერდება, თორემ აპი მკვდარ ეკრანზე უთითებს და callback განაგრძობს გაშვებას.
NetworkRequest req = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();
cm.registerNetworkCallback(req, callback);
ეს მოითხოვს ACCESS_NETWORK_STATE-ს, რომელიც ჩვეულებრივი ნებართვაა: root-ის გარეშე, მომხმარებლის თანხმობის დიალოგის გარეშე, VPN-ის ნებართვის გარეშე. ტაიმერზე გამოკითხვა ის ალტერნატივაა, რომელსაც ხალხი მიმართავს, და ის ხარჯავს ბატარეას და მაინც გამოტოვებს ინტერვალზე მოკლე გადასვლას, რაც ზუსტად ის გადასვლაა, რომელზეც მომხმარებელი იტყობინება.
რისი თქმაც პასუხს ჯერ არ შეუძლია
არც ერთი ეს API არ აცხადებს, ჯანსაღია თუ არა გვირაბი, რომელ სერვერზე მთავრდება, ან კონკრეტული აპის ტრაფიკი მის შიგნითაა თუ არა. VPN ქსელი შეიძლება არსებობდეს გამოსადეგი მარშრუტების გარეშე, და აპლიკაციისთვის განკუთვნილმა გვირაბმა შეიძლება გამორიცხოს კითხვის დამსმელი აპი.
ამიტომ ჩაწერეთ, რა დაინახა შემოწმებამ და არა მხოლოდ ბულის მნიშვნელობა. ჩაწერეთ ქსელის იდენტიფიკატორი, გამოცხადებული ტრანსპორტები და ის, იყო თუ არა ეს ქსელი აქტიური. ანგარიში, რომელიც ამბობს „VPN ვერ აღმოჩნდა“, უვარგისია, მაშინ როცა ანგარიში, რომელიც ამბობს, რომ ქსელმა გამოაცხადა TRANSPORT_VPN, მაგრამ getActiveNetwork()-მა დააბრუნა Wi-Fi, პირდაპირ მიუთითებს მარშრუტიზაციაზე ან გამორიცხვების სიაზე, და ეს ის გასწორებაა, რომელზეც ვინმეს შეუძლია იმუშაოს.